katalyst-navigation 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +25 -0
- data/app/assets/config/katalyst-navigation.js +1 -0
- data/app/assets/javascripts/controllers/navigation/editor/item_controller.js +45 -0
- data/app/assets/javascripts/controllers/navigation/editor/list_controller.js +105 -0
- data/app/assets/javascripts/controllers/navigation/editor/menu_controller.js +110 -0
- data/app/assets/javascripts/controllers/navigation/editor/new_item_controller.js +12 -0
- data/app/assets/javascripts/controllers/navigation/editor/status_bar_controller.js +22 -0
- data/app/assets/javascripts/utils/navigation/editor/item.js +245 -0
- data/app/assets/javascripts/utils/navigation/editor/menu.js +54 -0
- data/app/assets/javascripts/utils/navigation/editor/rules-engine.js +140 -0
- data/app/assets/stylesheets/katalyst/navigation/editor/_icon.scss +17 -0
- data/app/assets/stylesheets/katalyst/navigation/editor/_index.scss +145 -0
- data/app/assets/stylesheets/katalyst/navigation/editor/_item-actions.scss +92 -0
- data/app/assets/stylesheets/katalyst/navigation/editor/_item-rules.scss +24 -0
- data/app/assets/stylesheets/katalyst/navigation/editor/_new-items.scss +22 -0
- data/app/assets/stylesheets/katalyst/navigation/editor/_status-bar.scss +87 -0
- data/app/controllers/katalyst/navigation/base_controller.rb +12 -0
- data/app/controllers/katalyst/navigation/items_controller.rb +57 -0
- data/app/controllers/katalyst/navigation/menus_controller.rb +82 -0
- data/app/helpers/katalyst/navigation/editor/base.rb +41 -0
- data/app/helpers/katalyst/navigation/editor/item.rb +69 -0
- data/app/helpers/katalyst/navigation/editor/list.rb +41 -0
- data/app/helpers/katalyst/navigation/editor/menu.rb +37 -0
- data/app/helpers/katalyst/navigation/editor/new_item.rb +53 -0
- data/app/helpers/katalyst/navigation/editor/status_bar.rb +57 -0
- data/app/helpers/katalyst/navigation/editor_helper.rb +46 -0
- data/app/helpers/katalyst/navigation/frontend/builder.rb +53 -0
- data/app/helpers/katalyst/navigation/frontend_helper.rb +42 -0
- data/app/models/concerns/katalyst/navigation/garbage_collection.rb +31 -0
- data/app/models/concerns/katalyst/navigation/has_tree.rb +63 -0
- data/app/models/katalyst/navigation/button.rb +15 -0
- data/app/models/katalyst/navigation/item.rb +21 -0
- data/app/models/katalyst/navigation/link.rb +10 -0
- data/app/models/katalyst/navigation/menu.rb +123 -0
- data/app/models/katalyst/navigation/node.rb +21 -0
- data/app/models/katalyst/navigation/types/nodes_type.rb +42 -0
- data/app/views/katalyst/navigation/items/_button.html.erb +28 -0
- data/app/views/katalyst/navigation/items/_link.html.erb +21 -0
- data/app/views/katalyst/navigation/items/edit.html.erb +4 -0
- data/app/views/katalyst/navigation/items/new.html.erb +4 -0
- data/app/views/katalyst/navigation/items/update.turbo_stream.erb +7 -0
- data/app/views/katalyst/navigation/menus/_item.html.erb +15 -0
- data/app/views/katalyst/navigation/menus/_list_item.html.erb +14 -0
- data/app/views/katalyst/navigation/menus/_new_item.html.erb +3 -0
- data/app/views/katalyst/navigation/menus/_new_items.html.erb +5 -0
- data/app/views/katalyst/navigation/menus/edit.html.erb +15 -0
- data/app/views/katalyst/navigation/menus/index.html.erb +17 -0
- data/app/views/katalyst/navigation/menus/new.html.erb +15 -0
- data/app/views/katalyst/navigation/menus/show.html.erb +15 -0
- data/config/importmap.rb +5 -0
- data/config/locales/en.yml +12 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20220826034057_create_katalyst_navigation_menus.rb +25 -0
- data/db/migrate/20220826034507_create_katalyst_navigation_items.rb +17 -0
- data/lib/katalyst/navigation/engine.rb +36 -0
- data/lib/katalyst/navigation/version.rb +7 -0
- data/lib/katalyst/navigation.rb +9 -0
- data/lib/tasks/yarn.rake +18 -0
- data/spec/factories/katalyst/navigation/items.rb +14 -0
- data/spec/factories/katalyst/navigation/menus.rb +17 -0
- metadata +109 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
export default class RulesEngine {
|
2
|
+
static rules = [
|
3
|
+
"denyDeNest",
|
4
|
+
"denyNest",
|
5
|
+
"denyCollapse",
|
6
|
+
"denyExpand",
|
7
|
+
"denyRemove",
|
8
|
+
"denyDrag",
|
9
|
+
"denyEdit",
|
10
|
+
"invalidDepth",
|
11
|
+
];
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Apply rules to the given item by computing a ruleset then merging it
|
15
|
+
* with the item's current state.
|
16
|
+
*
|
17
|
+
* @param {Item} item
|
18
|
+
*/
|
19
|
+
update(item) {
|
20
|
+
this.rules = {};
|
21
|
+
|
22
|
+
this.firstItemDepthZero(item);
|
23
|
+
this.depthMustBeSet(item);
|
24
|
+
this.parentsCannotDeNest(item);
|
25
|
+
this.rootsCannotDeNest(item);
|
26
|
+
this.nestingNeedsParent(item);
|
27
|
+
this.leavesCannotCollapse(item);
|
28
|
+
this.needHiddenItemsToExpand(item);
|
29
|
+
this.parentsCannotBeDeleted(item);
|
30
|
+
this.parentsCannotBeDragged(item);
|
31
|
+
this.itemCannotHaveInvalidDepth(item);
|
32
|
+
|
33
|
+
RulesEngine.rules.forEach((rule) => {
|
34
|
+
item.toggleRule(rule, !!this.rules[rule]);
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* First item can't have a parent, so its depth should always be 0
|
40
|
+
*/
|
41
|
+
firstItemDepthZero(item) {
|
42
|
+
if (item.index === 0) {
|
43
|
+
item.depth = 0;
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Every item should have a non-negative depth set.
|
49
|
+
*
|
50
|
+
* @param {Item} item
|
51
|
+
*/
|
52
|
+
depthMustBeSet(item) {
|
53
|
+
if (isNaN(item.depth) || item.depth < 0) {
|
54
|
+
item.depth = 0;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* De-nesting an item would create a gap of 2 between itself and its children
|
60
|
+
*
|
61
|
+
* @param {Item} item
|
62
|
+
*/
|
63
|
+
parentsCannotDeNest(item) {
|
64
|
+
if (item.hasExpandedDescendants()) this.#deny("denyDeNest");
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Item depth can't go below 0.
|
69
|
+
*
|
70
|
+
* @param {Item} item
|
71
|
+
*/
|
72
|
+
rootsCannotDeNest(item) {
|
73
|
+
if (item.depth === 0) this.#deny("denyDeNest");
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* If an item doesn't have children it can't be collapsed.
|
78
|
+
*
|
79
|
+
* @param {Item} item
|
80
|
+
*/
|
81
|
+
leavesCannotCollapse(item) {
|
82
|
+
if (!item.hasExpandedDescendants()) this.#deny("denyCollapse");
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* If an item doesn't have any hidden descendants then it can't be expanded.
|
87
|
+
*
|
88
|
+
* @param {Item} item
|
89
|
+
*/
|
90
|
+
needHiddenItemsToExpand(item) {
|
91
|
+
if (!item.hasCollapsedDescendants()) this.#deny("denyExpand");
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* An item can't be nested (indented) if it doesn't have a valid parent.
|
96
|
+
*
|
97
|
+
* @param {Item} item
|
98
|
+
*/
|
99
|
+
nestingNeedsParent(item) {
|
100
|
+
const previous = item.previousItem;
|
101
|
+
if (!previous || previous.depth < item.depth) this.#deny("denyNest");
|
102
|
+
}
|
103
|
+
|
104
|
+
/**
|
105
|
+
* An item can't be deleted if it has visible children.
|
106
|
+
*
|
107
|
+
* @param {Item} item
|
108
|
+
*/
|
109
|
+
parentsCannotBeDeleted(item) {
|
110
|
+
if (item.hasExpandedDescendants()) this.#deny("denyRemove");
|
111
|
+
}
|
112
|
+
|
113
|
+
/**
|
114
|
+
* Items cannot be dragged if they have visible children.
|
115
|
+
*
|
116
|
+
* @param {Item} item
|
117
|
+
*/
|
118
|
+
parentsCannotBeDragged(item) {
|
119
|
+
if (item.hasExpandedDescendants()) this.#deny("denyDrag");
|
120
|
+
}
|
121
|
+
|
122
|
+
/**
|
123
|
+
* Depth must increase stepwise.
|
124
|
+
*
|
125
|
+
* @param {Item} item
|
126
|
+
*/
|
127
|
+
itemCannotHaveInvalidDepth(item) {
|
128
|
+
const previous = item.previousItem;
|
129
|
+
if (previous && previous.depth < item.depth - 1) this.#deny("invalidDepth");
|
130
|
+
}
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Record a deny.
|
134
|
+
*
|
135
|
+
* @param rule {String}
|
136
|
+
*/
|
137
|
+
#deny(rule) {
|
138
|
+
this.rules[rule] = true;
|
139
|
+
}
|
140
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
%icon-block {
|
2
|
+
display: block;
|
3
|
+
cursor: pointer;
|
4
|
+
position: relative;
|
5
|
+
padding: 0.65rem;
|
6
|
+
min-width: 2.5rem;
|
7
|
+
min-height: 2.5rem;
|
8
|
+
}
|
9
|
+
|
10
|
+
%icon {
|
11
|
+
position: absolute;
|
12
|
+
content: "";
|
13
|
+
width: 1.2rem;
|
14
|
+
height: 1.2rem;
|
15
|
+
background-repeat: no-repeat;
|
16
|
+
background-position: center;
|
17
|
+
}
|
@@ -0,0 +1,145 @@
|
|
1
|
+
@use "icon";
|
2
|
+
|
3
|
+
@use "item-actions";
|
4
|
+
@use "item-rules";
|
5
|
+
@use "new-items";
|
6
|
+
@use "status-bar";
|
7
|
+
|
8
|
+
$grey-light: #f4f4f4 !default;
|
9
|
+
$grey: #ececec !default;
|
10
|
+
$grey-dark: #999 !default;
|
11
|
+
$table-hover-background: #fff0eb !default;
|
12
|
+
$primary-color: #ff521f !default;
|
13
|
+
|
14
|
+
$row-inset: 2rem !default;
|
15
|
+
$row-height: 3rem !default;
|
16
|
+
|
17
|
+
$table-header-color: $grey !default;
|
18
|
+
$row-background-color: $grey-light !default;
|
19
|
+
$row-hover-color: $table-hover-background !default;
|
20
|
+
$icon-active-color: $primary-color !default;
|
21
|
+
$icon-passive-color: $grey-dark !default;
|
22
|
+
|
23
|
+
$status-published-background-color: #ebf9eb !default;
|
24
|
+
$status-published-border-color: #4dd45c !default;
|
25
|
+
$status-published-color: #4dd45c !default;
|
26
|
+
|
27
|
+
$status-draft-background-color: #fefaf3 !default;
|
28
|
+
$status-draft-border-color: #ffa800 !default;
|
29
|
+
$status-draft-color: #ffa800 !default;
|
30
|
+
|
31
|
+
$status-dirty-background-color: #eee !default;
|
32
|
+
$status-dirty-border-color: #888 !default;
|
33
|
+
$status-dirty-color: #aaa !default;
|
34
|
+
|
35
|
+
[data-controller="navigation--editor--menu"] {
|
36
|
+
--row-height: #{$row-height};
|
37
|
+
--row-inset: #{$row-inset};
|
38
|
+
--table-header-color: #{$table-header-color};
|
39
|
+
--row-background-color: #{$row-background-color};
|
40
|
+
--row-hover-color: #{$row-hover-color};
|
41
|
+
--icon-active-color: #{$icon-active-color};
|
42
|
+
--icon-passive-color: #{$icon-passive-color};
|
43
|
+
|
44
|
+
ol,
|
45
|
+
li {
|
46
|
+
margin: 0;
|
47
|
+
padding: 0;
|
48
|
+
padding-inline-start: 0;
|
49
|
+
list-style: none;
|
50
|
+
}
|
51
|
+
|
52
|
+
.hidden {
|
53
|
+
display: none !important;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
[data-controller="navigation--editor--list"] {
|
58
|
+
min-height: var(--row-height);
|
59
|
+
|
60
|
+
// tree items
|
61
|
+
& > li {
|
62
|
+
display: block;
|
63
|
+
min-height: var(--row-height);
|
64
|
+
|
65
|
+
// https://github.com/react-dnd/react-dnd/issues/832
|
66
|
+
transform: translate3d(0, 0, 0);
|
67
|
+
|
68
|
+
// Pinstripe effect
|
69
|
+
&:nth-of-type(even) {
|
70
|
+
background: var(--row-background-color);
|
71
|
+
}
|
72
|
+
|
73
|
+
&:hover {
|
74
|
+
background: var(--row-hover-color);
|
75
|
+
}
|
76
|
+
|
77
|
+
&[draggable] {
|
78
|
+
cursor: grab;
|
79
|
+
}
|
80
|
+
|
81
|
+
// Dragged visuals
|
82
|
+
&[data-dragging] {
|
83
|
+
border: 2px dashed;
|
84
|
+
|
85
|
+
> * {
|
86
|
+
visibility: hidden;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
// Depth spacing
|
91
|
+
@for $i from 1 through 6 {
|
92
|
+
&[data-navigation-depth="#{$i}"] .tree {
|
93
|
+
padding-left: calc(var(--row-inset) * #{$i});
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
.tree {
|
98
|
+
display: flex;
|
99
|
+
align-items: center;
|
100
|
+
}
|
101
|
+
|
102
|
+
.title,
|
103
|
+
.url {
|
104
|
+
text-overflow: ellipsis;
|
105
|
+
overflow: hidden;
|
106
|
+
white-space: nowrap;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
[data-controller="navigation--editor--menu"] [role="rowheader"],
|
112
|
+
[data-controller="navigation--editor--item"] {
|
113
|
+
display: grid;
|
114
|
+
grid-template-columns: 40% 2fr auto;
|
115
|
+
padding: 0.25rem 0.5rem;
|
116
|
+
gap: 1rem;
|
117
|
+
align-items: center;
|
118
|
+
}
|
119
|
+
|
120
|
+
// Ensures vertical alignment of header with rows
|
121
|
+
[data-controller="navigation--editor-menu"] {
|
122
|
+
[role="rowheader"] {
|
123
|
+
min-height: var(--row-height);
|
124
|
+
background: var(--table-header-color);
|
125
|
+
padding-inline: 1.25rem 1rem;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
[data-controller="navigation--editor--status-bar"] {
|
130
|
+
--background: #{$status-published-background-color};
|
131
|
+
--color: #{$status-published-border-color};
|
132
|
+
--border: #{$status-published-color};
|
133
|
+
|
134
|
+
&[data-state="draft"] {
|
135
|
+
--background: #{$status-draft-background-color};
|
136
|
+
--color: #{$status-draft-border-color};
|
137
|
+
--border: #{$status-draft-color};
|
138
|
+
}
|
139
|
+
|
140
|
+
&[data-state="dirty"] {
|
141
|
+
--background: #{$status-dirty-background-color};
|
142
|
+
--color: #{$status-dirty-border-color};
|
143
|
+
--border: #{$status-dirty-color};
|
144
|
+
}
|
145
|
+
}
|
@@ -0,0 +1,92 @@
|
|
1
|
+
@use "icon" as *;
|
2
|
+
|
3
|
+
[data-controller="navigation--editor--item"] {
|
4
|
+
[data-tree-accordion-controls] {
|
5
|
+
min-width: 2.5rem;
|
6
|
+
min-height: 2.5rem;
|
7
|
+
display: grid;
|
8
|
+
}
|
9
|
+
|
10
|
+
[data-tree-controls] {
|
11
|
+
display: flex;
|
12
|
+
align-items: center;
|
13
|
+
}
|
14
|
+
|
15
|
+
[data-tree-accordion-controls],
|
16
|
+
[data-tree-controls] {
|
17
|
+
[role="button"] {
|
18
|
+
@extend %icon-block;
|
19
|
+
&::before {
|
20
|
+
@extend %icon;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
[role="img"][value="link"],
|
26
|
+
[role="img"][value="title"] {
|
27
|
+
width: 1.5rem;
|
28
|
+
height: 1.5rem;
|
29
|
+
display: grid;
|
30
|
+
place-items: center;
|
31
|
+
border-radius: 2px;
|
32
|
+
background: var(--icon-active-color);
|
33
|
+
margin-right: 0.5rem;
|
34
|
+
|
35
|
+
[data-invisible="true"] & {
|
36
|
+
background: var(--icon-passive-color);
|
37
|
+
}
|
38
|
+
|
39
|
+
&::before {
|
40
|
+
@extend %icon;
|
41
|
+
color: white;
|
42
|
+
font-size: 1.125rem;
|
43
|
+
line-height: 1.125rem;
|
44
|
+
text-align: center;
|
45
|
+
}
|
46
|
+
|
47
|
+
&[value="link"] {
|
48
|
+
&::before {
|
49
|
+
content: "#";
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
&[value="title"] {
|
54
|
+
&::before {
|
55
|
+
content: "T";
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
[role="img"][value="invisible"] {
|
61
|
+
@extend %icon-block;
|
62
|
+
|
63
|
+
&::before {
|
64
|
+
@extend %icon;
|
65
|
+
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_58_2189)'%3E%3Cpath d='M22.1699 0.329991C21.7304 -0.109509 21.0179 -0.109509 20.5784 0.329991L15.8399 5.06849C14.6219 4.69949 13.3334 4.50149 11.9984 4.50149C6.76494 4.50149 2.22744 7.54949 -0.00156052 12.0015C0.962939 13.926 2.35794 15.588 4.05294 16.8555L0.326939 20.5815C-0.112561 21.021 -0.112561 21.7335 0.326939 22.173C0.545939 22.392 0.833939 22.503 1.12194 22.503C1.40994 22.503 1.69794 22.3935 1.91694 22.173L22.1669 1.92299C22.6064 1.48349 22.6064 0.770991 22.1669 0.331491L22.1699 0.329991ZM9.74994 7.49999C10.7399 7.49999 11.5799 8.13899 11.8814 9.02849L9.02844 11.8815C8.14044 11.58 7.49994 10.74 7.49994 9.74999C7.49994 8.50799 8.50794 7.49999 9.74994 7.49999ZM2.58144 12C3.47844 10.581 4.67394 9.37649 6.08394 8.47799C6.17544 8.41949 6.26844 8.36249 6.36144 8.30699C6.12744 8.94749 5.99994 9.63899 5.99994 10.3605C5.99994 11.6475 6.40494 12.8385 7.09494 13.815L5.66694 15.243C4.43844 14.379 3.38844 13.2765 2.58144 12V12Z' fill='%2358607A'/%3E%3Cpath d='M18.0001 10.3589C18.0001 9.72295 17.9011 9.10945 17.7166 8.53345L10.1746 16.0754C10.7506 16.2599 11.3641 16.3589 12.0001 16.3589C15.3136 16.3589 18.0001 13.6724 18.0001 10.3589V10.3589Z' fill='%2358607A'/%3E%3Cpath d='M19.4536 6.79651L17.8276 8.42251C17.8576 8.44051 17.8876 8.45851 17.9161 8.47801C19.3261 9.37801 20.5216 10.5825 21.4186 12C20.5216 13.419 19.3261 14.6235 17.9161 15.522C16.1446 16.6515 14.0986 17.25 12.0001 17.25C11.0941 17.25 10.1971 17.139 9.3286 16.9215L7.5271 18.723C8.9266 19.2255 10.4326 19.5 12.0001 19.5C17.2336 19.5 21.7711 16.452 24.0001 12C22.9456 9.89251 21.3721 8.10001 19.4536 6.79651V6.79651Z' fill='%2358607A'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_58_2189'%3E%3Crect width='24' height='24' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
[role="button"][value="collapse"]::before {
|
70
|
+
background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_50_1454)'%3E%3Cpath d='M15 8.33333L13.825 7.15833L10 10.975L6.175 7.15833L5 8.33333L10 13.3333L15 8.33333Z' fill='%2358607A'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_50_1454'%3E%3Crect width='20' height='20' fill='white' transform='translate(20) rotate(90)'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
|
71
|
+
}
|
72
|
+
|
73
|
+
[role="button"][value="expand"]::before {
|
74
|
+
background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_50_1454)'%3E%3Cpath d='M8.3332 5L7.1582 6.175L10.9749 10L7.1582 13.825L8.3332 15L13.3332 10L8.3332 5Z' fill='%2358607A'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_50_1454'%3E%3Crect width='20' height='20' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
|
75
|
+
}
|
76
|
+
|
77
|
+
[role="button"][value="de-nest"]::before {
|
78
|
+
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_50_1454)'%3E%3Cpath d='M11.6668 15L12.8418 13.825L9.02513 10L12.8418 6.175L11.6668 5L6.6668 10L11.6668 15Z' fill='%2358607A'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_50_1454'%3E%3Crect width='20' height='20' fill='white' transform='translate(20 20) rotate(180)'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
|
79
|
+
}
|
80
|
+
|
81
|
+
[role="button"][value="nest"]::before {
|
82
|
+
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_50_1454)'%3E%3Cpath d='M8.3332 5L7.1582 6.175L10.9749 10L7.1582 13.825L8.3332 15L13.3332 10L8.3332 5Z' fill='%2358607A'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_50_1454'%3E%3Crect width='20' height='20' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
|
83
|
+
}
|
84
|
+
|
85
|
+
[role="button"][value="edit"]::before {
|
86
|
+
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_50_1406)'%3E%3Cpath d='M2.25 12.9375V15.75H5.0625L13.3575 7.45504L10.545 4.64254L2.25 12.9375ZM15.5325 5.28004C15.825 4.98754 15.825 4.51504 15.5325 4.22254L13.7775 2.46754C13.485 2.17504 13.0125 2.17504 12.72 2.46754L11.3475 3.84004L14.16 6.65254L15.5325 5.28004V5.28004Z' fill='%2358607A'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_50_1406'%3E%3Crect width='18' height='18' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
|
87
|
+
}
|
88
|
+
|
89
|
+
[role="button"][value="remove"]::before {
|
90
|
+
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_50_1409)'%3E%3Cpath d='M4.5 14.25C4.5 15.075 5.175 15.75 6 15.75H12C12.825 15.75 13.5 15.075 13.5 14.25V5.25H4.5V14.25ZM14.25 3H11.625L10.875 2.25H7.125L6.375 3H3.75V4.5H14.25V3Z' fill='%2358607A'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_50_1409'%3E%3Crect width='18' height='18' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
|
91
|
+
}
|
92
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
[data-controller="navigation--editor--list"] {
|
2
|
+
// Lower opacity for buttons you can't use
|
3
|
+
[data-deny-de-nest] [role="button"][value="de-nest"],
|
4
|
+
[data-deny-nest] [role="button"][value="nest"],
|
5
|
+
[data-deny-remove] [role="button"][value="remove"],
|
6
|
+
[data-deny-drag] [role="button"][value="drag"],
|
7
|
+
[data-deny-edit] [role="button"][value="edit"] {
|
8
|
+
opacity: 0.2;
|
9
|
+
pointer-events: none;
|
10
|
+
}
|
11
|
+
|
12
|
+
// Only show 1 of the collapse / expand button
|
13
|
+
[data-deny-collapse] [role="button"][value="collapse"],
|
14
|
+
[data-deny-expand] [role="button"][value="expand"],
|
15
|
+
[data-invisible="false"] [role="img"][value="invisible"] {
|
16
|
+
display: none !important;
|
17
|
+
pointer-events: none;
|
18
|
+
}
|
19
|
+
|
20
|
+
[data-invalid-depth] {
|
21
|
+
border-color: red;
|
22
|
+
background-color: indianred;
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
@use "icon" as *;
|
2
|
+
|
3
|
+
.navigation--editor--new-items {
|
4
|
+
display: grid;
|
5
|
+
grid-template-columns: repeat(3, 1fr);
|
6
|
+
gap: 0.25rem;
|
7
|
+
|
8
|
+
[role="listitem"] {
|
9
|
+
display: flex;
|
10
|
+
flex-direction: column;
|
11
|
+
justify-content: center;
|
12
|
+
align-items: center;
|
13
|
+
transform: translate3d(0, 0, 0);
|
14
|
+
cursor: grab;
|
15
|
+
|
16
|
+
&::before {
|
17
|
+
@extend %icon;
|
18
|
+
position: unset;
|
19
|
+
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16 3.66667V13.3333C16 14.8 14.6909 16 13.0909 16H7.78182C6.99636 16 6.25455 15.7133 5.70909 15.2067L0 9.88667C0 9.88667 0.916364 9.06667 0.945455 9.05333C1.10545 8.92667 1.30182 8.86 1.52 8.86C1.68 8.86 1.82545 8.9 1.95636 8.96667C1.98545 8.97333 5.09091 10.6067 5.09091 10.6067V2.66667C5.09091 2.11333 5.57818 1.66667 6.18182 1.66667C6.78545 1.66667 7.27273 2.11333 7.27273 2.66667V7.33333H8V1C8 0.446667 8.48727 0 9.09091 0C9.69455 0 10.1818 0.446667 10.1818 1V7.33333H10.9091V1.66667C10.9091 1.11333 11.3964 0.666667 12 0.666667C12.6036 0.666667 13.0909 1.11333 13.0909 1.66667V7.33333H13.8182V3.66667C13.8182 3.11333 14.3055 2.66667 14.9091 2.66667C15.5127 2.66667 16 3.11333 16 3.66667Z' fill='white'/%3E%3C/svg%3E");
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
[data-controller="navigation--editor--status-bar"] {
|
2
|
+
min-height: 3rem;
|
3
|
+
line-height: 3rem;
|
4
|
+
padding: 0 1.25rem 0 1.5rem;
|
5
|
+
background: var(--background);
|
6
|
+
color: var(--color);
|
7
|
+
border: 1px solid var(--border);
|
8
|
+
margin-bottom: 1rem;
|
9
|
+
|
10
|
+
display: grid;
|
11
|
+
grid-template-columns: 1fr auto;
|
12
|
+
grid-template-areas: "status actions";
|
13
|
+
align-items: baseline;
|
14
|
+
grid-column-gap: 2rem;
|
15
|
+
|
16
|
+
.status-text {
|
17
|
+
display: none;
|
18
|
+
grid-area: status;
|
19
|
+
font-weight: bold;
|
20
|
+
}
|
21
|
+
|
22
|
+
&[data-state="published"] .status-text[data-published],
|
23
|
+
&[data-state="draft"] .status-text[data-draft],
|
24
|
+
&[data-state="dirty"] .status-text[data-dirty] {
|
25
|
+
display: unset;
|
26
|
+
}
|
27
|
+
|
28
|
+
menu {
|
29
|
+
display: inline;
|
30
|
+
grid-area: actions;
|
31
|
+
margin: 0;
|
32
|
+
padding: 0;
|
33
|
+
}
|
34
|
+
|
35
|
+
menu > li {
|
36
|
+
display: inline;
|
37
|
+
}
|
38
|
+
|
39
|
+
.button {
|
40
|
+
color: inherit;
|
41
|
+
line-height: 1rem;
|
42
|
+
margin-left: 0.5rem;
|
43
|
+
}
|
44
|
+
|
45
|
+
.button--primary {
|
46
|
+
background: var(--color);
|
47
|
+
border: 1px solid var(--border);
|
48
|
+
color: white;
|
49
|
+
|
50
|
+
&[disabled] {
|
51
|
+
background: var(--color);
|
52
|
+
opacity: 0.8;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
.button--secondary {
|
57
|
+
background: var(--background);
|
58
|
+
border: 1px solid var(--border);
|
59
|
+
color: var(--color);
|
60
|
+
|
61
|
+
&[disabled] {
|
62
|
+
background: var(--background);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
&[data-state="published"] {
|
67
|
+
[value="publish"],
|
68
|
+
[value="save"],
|
69
|
+
[value="revert"],
|
70
|
+
[value="discard"] {
|
71
|
+
display: none;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
&[data-state="draft"] {
|
76
|
+
[value="save"],
|
77
|
+
[value="discard"] {
|
78
|
+
display: none;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
&[data-state="dirty"] {
|
83
|
+
[value="revert"] {
|
84
|
+
display: none;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Navigation
|
5
|
+
class ItemsController < BaseController
|
6
|
+
before_action :set_menu
|
7
|
+
before_action :set_item, except: %i[new create]
|
8
|
+
|
9
|
+
def new
|
10
|
+
render locals: { item: @menu.items.build(type: new_item_params) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
item = @menu.items.build(item_params)
|
15
|
+
if item.save
|
16
|
+
render :update, locals: { item: item, previous: @menu.items.build(type: item.type) }
|
17
|
+
else
|
18
|
+
render :new, status: :unprocessable_entity, locals: { item: item }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def edit
|
23
|
+
render locals: { item: @item }
|
24
|
+
end
|
25
|
+
|
26
|
+
def update
|
27
|
+
@item.attributes = item_params
|
28
|
+
|
29
|
+
if @item.valid?
|
30
|
+
previous = @item
|
31
|
+
@item = @item.dup.tap(&:save!)
|
32
|
+
render locals: { item: @item, previous: previous }
|
33
|
+
else
|
34
|
+
render :edit, status: :unprocessable_entity, locals: { item: @item }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def new_item_params
|
41
|
+
params[:type] || Link.name
|
42
|
+
end
|
43
|
+
|
44
|
+
def item_params
|
45
|
+
params.require(:item).permit(%i[title url visible http_method new_tab type])
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_menu
|
49
|
+
@menu = Menu.find(params[:menu_id])
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_item
|
53
|
+
@item = @menu.items.find(params[:id])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Katalyst
|
4
|
+
module Navigation
|
5
|
+
class MenusController < BaseController
|
6
|
+
def index
|
7
|
+
sort, menus = table_sort(Menu.all)
|
8
|
+
|
9
|
+
render locals: { menus: menus, sort: sort }
|
10
|
+
end
|
11
|
+
|
12
|
+
def new
|
13
|
+
render locals: { menu: Menu.new }
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
@menu = Menu.new(menu_params)
|
18
|
+
|
19
|
+
if @menu.save
|
20
|
+
redirect_to @menu
|
21
|
+
else
|
22
|
+
render :new, locals: { menu: @menu }, status: :unprocessable_entity
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def show
|
27
|
+
menu = Menu.find(params[:id])
|
28
|
+
|
29
|
+
render locals: { menu: menu }
|
30
|
+
end
|
31
|
+
|
32
|
+
def edit
|
33
|
+
menu = Menu.find(params[:id])
|
34
|
+
|
35
|
+
render locals: { menu: menu }
|
36
|
+
end
|
37
|
+
|
38
|
+
# PATCH /admins/navigation_menus/:slug
|
39
|
+
def update
|
40
|
+
menu = Menu.find(params[:id])
|
41
|
+
|
42
|
+
menu.attributes = navigation_params
|
43
|
+
|
44
|
+
unless menu.valid?
|
45
|
+
return render :show, locals: { menu: menu }, status: :unprocessable_entity
|
46
|
+
end
|
47
|
+
|
48
|
+
case params[:commit]
|
49
|
+
when "publish"
|
50
|
+
menu.save!
|
51
|
+
menu.publish!
|
52
|
+
when "save"
|
53
|
+
menu.save!
|
54
|
+
when "revert"
|
55
|
+
menu.revert!
|
56
|
+
end
|
57
|
+
redirect_to menu
|
58
|
+
end
|
59
|
+
|
60
|
+
def destroy
|
61
|
+
menu = Menu.find(params[:id])
|
62
|
+
|
63
|
+
menu.destroy!
|
64
|
+
|
65
|
+
redirect_to action: :index
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def menu_params
|
71
|
+
params.require(:menu).permit(:title, :slug)
|
72
|
+
end
|
73
|
+
|
74
|
+
def navigation_params
|
75
|
+
return {} if params[:menu].blank?
|
76
|
+
|
77
|
+
params.require(:menu)
|
78
|
+
.permit(:title, :slug, items_attributes: %i[id index depth])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|