adminterface 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4069cfffd19de3ada098678538e0e7ca4f9a840c8477de55fe3612cd6f541572
4
- data.tar.gz: 7454d611f8fd759b502577cd30f1a00ee7f7c6587b2361155998ef1c2e29aff7
3
+ metadata.gz: 7aa4367f85794395d6b92b141c12488c2158b99f0585f4a76f201bb080fb167c
4
+ data.tar.gz: 88a7bb2ecafe63f80145908a0cd4d1289011a71352ee9960f2870a80c91fc08f
5
5
  SHA512:
6
- metadata.gz: d04bd12439c118daedfb64958cdcf0a3a24a077331147bc98376ed0a8df5de9eada6dadb7de0eb066690a2d979c0277a357b42cead3ccf2b4a7e9a4c81bb5972
7
- data.tar.gz: dc004f3881b891829f647c813ec8025459a76be580276b9bcb54ff5eafcd91a35324169b099b0ffb1eb588ea4f34820d41621027bcdff5f3e7509696c81d02f1
6
+ metadata.gz: 92dfa1e53f319043ee355df3d0d70e9888f070727c5828851ee1e10d6e2aa590af464dc7ad75ef871d8e375676dfc68ed4c97decc0fc3f0492868d642f2f2667
7
+ data.tar.gz: 650a23ef8b8f323f6f792fa591aec71d60701c7b5c32d3d2d311cb51ed73a42f9ef8bf0d988498d60aba2cfe0b8a87e82bd8aa7c8e5162b209ac6f72e5d60004
data/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # Adminterface <!-- omit in toc -->
2
2
  [![CI](https://github.com/CMDBrew/adminterface/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/CMDBrew/adminterface/actions/workflows/ci.yml)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/4dbedfdf21a2d675d2ca/maintainability)](https://codeclimate.com/github/CMDBrew/adminterface/maintainability)
4
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/4dbedfdf21a2d675d2ca/test_coverage)](https://codeclimate.com/github/CMDBrew/adminterface/test_coverage)
5
+ [![gem](https://img.shields.io/gem/v/adminterface?color=%23E9573F&label=rubygems.org)](https://rubygems.org/gems/adminterface)
6
+ [![npm](https://img.shields.io/npm/v/@cmdbrew/adminterface?color=%230273B4&label=npmjs.com)](https://www.npmjs.com/package/@cmdbrew/adminterface)
3
7
 
4
- An add-on that brings Bootstrap 5 and other goodies into ActiveAdmin.
8
+ A gem that brings Bootstrap 5, advanced customizability, and other goodies into ActiveAdmin
5
9
 
6
10
  [![](preview.jpg)](https://adminterface.io)
7
11
 
@@ -26,6 +30,10 @@ An add-on that brings Bootstrap 5 and other goodies into ActiveAdmin.
26
30
  $ rails g active_admin:install --use_webpacker
27
31
  ```
28
32
  4. Install Adminterface
33
+
34
+ > This command will ask for permissions to overwrite files generated by ActiveAdmin. Make sure to review the files carefully and accept them.
35
+ > We recommend you commit your files before running this command to ensure you are aware of the changes made.
36
+
29
37
  ```bash
30
38
  $ rails g adminterface:install
31
39
  ```
@@ -3,7 +3,7 @@ import HeaderToggler from '../lib/header_toggler'
3
3
  document.addEventListener('DOMContentLoaded', () => {
4
4
  const togglerTriggerList = [].slice.call(document.querySelectorAll('[data-aa-header-toggler]'))
5
5
  togglerTriggerList.map((el) => {
6
- const options = JSON.parse(el.dataset.aaHeaderToggler || {})
6
+ const options = JSON.parse(el.dataset.aaHeaderToggler || '{}')
7
7
 
8
8
  return new HeaderToggler(el, options)
9
9
  })
@@ -5,7 +5,7 @@ const initFlatpickr = (element) => {
5
5
  const flatpickrTriggerList = [].slice.call(element.querySelectorAll('[data-aa-flatpickr]'))
6
6
 
7
7
  flatpickrTriggerList.map((el) => {
8
- const options = JSON.parse(el.dataset.aaFlatpickr || {})
8
+ const options = JSON.parse(el.dataset.aaFlatpickr || '{}')
9
9
  const instance = flatpickr(el, options)
10
10
 
11
11
  adminterface.addObserver(el, instance, 'flatpickr')
@@ -4,7 +4,7 @@ const initInputCounter = function (element) {
4
4
  const inputCounterTriggerList = [].slice.call(element.querySelectorAll('[data-aa-input-counter]'))
5
5
 
6
6
  inputCounterTriggerList.map((el) => {
7
- const options = JSON.parse(el.dataset.aaInputCounter || {})
7
+ const options = JSON.parse(el.dataset.aaInputCounter || '{}')
8
8
 
9
9
  return new InputCounter(el, options)
10
10
  })
@@ -4,7 +4,7 @@ import initBootstrap from '../bootstrap'
4
4
  const initPasswordVisibility = function (element) {
5
5
  const visibilityTriggerList = [].slice.call(element.querySelectorAll('[data-aa-password-visibility]'))
6
6
  visibilityTriggerList.map((el) => {
7
- const options = JSON.parse(el.dataset.aaPasswordVisibility || {})
7
+ const options = JSON.parse(el.dataset.aaPasswordVisibility || '{}')
8
8
  const instance = new PasswordVisibility(el, options)
9
9
 
10
10
  initBootstrap(instance.eventTarget)
@@ -5,7 +5,7 @@ const initTomSelect = function (element) {
5
5
  const tomSelectTriggerList = [].slice.call(element.querySelectorAll('[data-aa-tom-select]'))
6
6
 
7
7
  tomSelectTriggerList.map((el) => {
8
- const options = JSON.parse(el.dataset.aaTomSelect || {})
8
+ const options = JSON.parse(el.dataset.aaTomSelect || '{}')
9
9
  const instance = new TomSelect(el, options)
10
10
 
11
11
  adminterface.addObserver(el, instance, instance.constructor.name)
@@ -0,0 +1,5 @@
1
+ <div id="find-me">
2
+ <input type="checkbox" class="toggle_all" checked>
3
+ <input type="checkbox" class="toggle_one" checked>
4
+ <input type="checkbox" class="toggle_two" checked>
5
+ </div>
@@ -0,0 +1,110 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import CheckboxTogglerClass from '../checkbox_toggler'
4
+
5
+ describe('CheckboxToggler', () => {
6
+ const html = fs.readFileSync(path.resolve(__dirname, './checkbox_toggler.html'))
7
+ const container = '#find-me'
8
+ const options = 'options'
9
+
10
+ beforeEach(() => {
11
+ document.documentElement.innerHTML = html.toString()
12
+ })
13
+
14
+ afterEach(() => {
15
+ jest.restoreAllMocks()
16
+ })
17
+
18
+ test('constructor initiates correctly', () => {
19
+ const spyOnInit = jest.spyOn(CheckboxTogglerClass.prototype, '_init')
20
+ const spyOnBind = jest.spyOn(CheckboxTogglerClass.prototype, '_bind')
21
+
22
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
23
+
24
+ expect(CheckboxToggler.options).toStrictEqual(options)
25
+ expect(CheckboxToggler.container).toStrictEqual(container)
26
+
27
+ expect(spyOnInit).toHaveBeenCalledTimes(1)
28
+ expect(spyOnBind).toHaveBeenCalledTimes(1)
29
+ })
30
+
31
+ test('init throws error if container is null', () => {
32
+ expect(() => { new CheckboxTogglerClass(null, options) }).toThrow( // eslint-disable-line no-new
33
+ 'Container element not found'
34
+ )
35
+ })
36
+
37
+ test('init throws error if toggle checkbox missing', () => {
38
+ const element = document.createElement('div')
39
+ element.id = 'find-me'
40
+ document.documentElement.innerHTML = element.outerHTML
41
+
42
+ expect(() => { new CheckboxTogglerClass(container, options) }).toThrow( // eslint-disable-line no-new
43
+ '"toggle all" checkbox not found'
44
+ )
45
+ })
46
+
47
+ test('init sets $container, $toggleAllCheckbox, $checkboxes', () => {
48
+ const $container = document.querySelector(container)
49
+ const $toggleAllCheckbox = $container.querySelector('.toggle_all')
50
+ const $checkboxes = $container.querySelectorAll('input[type="checkbox"]:not(.toggle_all)')
51
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
52
+
53
+ expect(CheckboxToggler.$container).toStrictEqual($container)
54
+ expect(CheckboxToggler.$toggleAllCheckbox).toStrictEqual($toggleAllCheckbox)
55
+ expect(CheckboxToggler.$checkboxes).toStrictEqual($checkboxes)
56
+ })
57
+
58
+ test('bind triggers addEventListener', () => {
59
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
60
+ const spyOnCheckboxesAddEvent = jest.spyOn(
61
+ CheckboxToggler.$checkboxes[0], 'addEventListener'
62
+ )
63
+ const spyOnToggleAllCheckboxAddEvent = jest.spyOn(
64
+ CheckboxToggler.$toggleAllCheckbox, 'addEventListener'
65
+ )
66
+
67
+ CheckboxToggler._bind()
68
+
69
+ expect(spyOnCheckboxesAddEvent).toHaveBeenCalledTimes(1)
70
+ expect(spyOnCheckboxesAddEvent).toHaveBeenCalledWith(
71
+ 'change',
72
+ expect.any(Function)
73
+ )
74
+
75
+ expect(spyOnToggleAllCheckboxAddEvent).toHaveBeenCalledTimes(1)
76
+ expect(spyOnToggleAllCheckboxAddEvent).toHaveBeenCalledWith(
77
+ 'change',
78
+ expect.any(Function)
79
+ )
80
+ })
81
+
82
+ test('didChangeCheckbox sets toggleAllCheckbox checked to true when all checked', () => {
83
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
84
+ CheckboxToggler._didChangeCheckbox()
85
+
86
+ expect(CheckboxToggler.$toggleAllCheckbox.checked).toBeTruthy()
87
+ expect(CheckboxToggler.$toggleAllCheckbox.indeterminate).toBeFalsy()
88
+ })
89
+
90
+ test('didChangeCheckbox sets toggleAllCheckbox checked to false when some checked', () => {
91
+ const inputElementToggleTwo = document.querySelector('input.toggle_two')
92
+ inputElementToggleTwo.checked = false
93
+
94
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
95
+ CheckboxToggler._didChangeCheckbox()
96
+
97
+ expect(CheckboxToggler.$toggleAllCheckbox.checked).toBeFalsy()
98
+ expect(CheckboxToggler.$toggleAllCheckbox.indeterminate).toBeTruthy()
99
+ })
100
+
101
+ test('didChangeToggleAllCheckbox', () => {
102
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
103
+ const setting = CheckboxToggler._didChangeToggleAllCheckbox()
104
+
105
+ expect(setting).toBeTruthy()
106
+ CheckboxToggler.$checkboxes.forEach(el => {
107
+ expect(el.checked).toStrictEqual(setting)
108
+ })
109
+ })
110
+ })
@@ -1,4 +1,3 @@
1
- /* eslint-env jest */
2
1
  import DetachedDropdownClass from '../detached_dropdown'
3
2
 
4
3
  describe('DetachedDropdown', () => {
@@ -1,4 +1,3 @@
1
- /* eslint-env jest */
2
1
  import Filters from '../filters'
3
2
  import * as utilModule from '../utils'
4
3
 
@@ -0,0 +1,14 @@
1
+ <div data-sortable="address">
2
+ <form class="has-many-list">
3
+ <fieldset class="has_many_fields">
4
+ <input type="checkbox" name="[_destroy]" checked>
5
+ <input type="text" name="[address]" value="Tokyo">
6
+ <a class="button has_many_remove"></a>
7
+ <a
8
+ class="button has_many_add"
9
+ data-html="<div></div>"
10
+ data-placeholder="script"
11
+ ></a>
12
+ </fieldset>
13
+ </form>
14
+ </div>
@@ -0,0 +1,224 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import Sortable from 'sortablejs'
4
+ import HasManyClass from '../has_many'
5
+
6
+ jest.mock('sortablejs')
7
+
8
+ describe('HasMany', () => {
9
+ const html = fs.readFileSync(path.resolve(__dirname, './has_many.html'))
10
+ let element
11
+
12
+ const options = {
13
+ list: '.has-many-list',
14
+ item: 'fieldset.has_many_fields',
15
+ addLink: 'a.button.has_many_add',
16
+ removeLink: 'a.button.has_many_remove',
17
+ destroyInput: 'input[name$="[_destroy]"]'
18
+ }
19
+
20
+ beforeEach(() => {
21
+ document.documentElement.innerHTML = html
22
+ element = document.querySelector('div')
23
+ global.adminterface = {
24
+ addObserver: jest.fn()
25
+ }
26
+ })
27
+
28
+ afterEach(() => {
29
+ jest.restoreAllMocks()
30
+ })
31
+
32
+ test('constructor initiates correctly', () => {
33
+ const spyOnBind = jest.spyOn(HasManyClass.prototype, '_bind')
34
+ spyOnBind.mockImplementation(() => {})
35
+
36
+ const HasMany = new HasManyClass(element)
37
+ expect(HasMany.element).toStrictEqual(element)
38
+
39
+ expect(spyOnBind).toHaveBeenCalledTimes(1)
40
+ })
41
+
42
+ test('bind calls bindEvents, initSortable and admininterface', () => {
43
+ const spyOnBindEvents = jest.spyOn(HasManyClass.prototype, '_bindEvents')
44
+ spyOnBindEvents.mockImplementation(() => {})
45
+
46
+ const spyOnInitSortable = jest.spyOn(HasManyClass.prototype, '_initSortable')
47
+ spyOnInitSortable.mockImplementation(() => {})
48
+
49
+ const HasMany = new HasManyClass(element)
50
+
51
+ expect(spyOnBindEvents).toHaveBeenCalledWith(element)
52
+ expect(spyOnInitSortable).toHaveBeenCalledTimes(1)
53
+ expect(global.adminterface.addObserver).toHaveBeenCalledTimes(1)
54
+ expect(global.adminterface.addObserver).toHaveBeenCalledWith(
55
+ element,
56
+ HasMany,
57
+ HasMany.constructor.name,
58
+ []
59
+ )
60
+ })
61
+
62
+ test('initSortable assigns new instance of Sortable to sortable property', () => {
63
+ const spyOnBindEvents = jest.spyOn(HasManyClass.prototype, '_bindEvents')
64
+ spyOnBindEvents.mockImplementation(() => {})
65
+
66
+ const HasMany = new HasManyClass(element)
67
+
68
+ const MockSortableArgument = {
69
+ handle: '.handle',
70
+ animation: 150,
71
+ ghostClass: 'sortable-placeholder',
72
+ dragClass: 'sortable-drag',
73
+ onUpdate: expect.any(Function)
74
+ }
75
+
76
+ expect(Sortable).toHaveBeenCalledTimes(1)
77
+ expect(Sortable).toHaveBeenCalledWith(
78
+ document.querySelector(HasMany.options.list),
79
+ MockSortableArgument
80
+ )
81
+ const sortable = new Sortable(
82
+ document.querySelector(HasMany.options.list),
83
+ MockSortableArgument
84
+ )
85
+ expect(HasMany.sortable.toString()).toStrictEqual(sortable.toString())
86
+ })
87
+
88
+ test('recomputePosition changes sortableInput value', () => {
89
+ const query = 'input[name$="[address]"]'
90
+ const sortableInput = element.querySelector(query)
91
+
92
+ expect(sortableInput.value.length).not.toEqual(0)
93
+
94
+ const HasMany = new HasManyClass(element)
95
+ HasMany._recomputePosition()
96
+
97
+ expect(sortableInput.value.length).toEqual(0)
98
+ })
99
+
100
+ test('bindDestroyEvent called with element and change event added', () => {
101
+ const spyOnBindDestroyEvent = jest.spyOn(
102
+ HasManyClass.prototype,
103
+ '_bindDestroyEvent'
104
+ )
105
+ const destroyInput = document.querySelector(options.destroyInput)
106
+ destroyInput.addEventListener = jest.fn()
107
+
108
+ new HasManyClass(element) // eslint-disable-line no-new
109
+
110
+ expect(spyOnBindDestroyEvent).toHaveBeenCalledTimes(1)
111
+ expect(spyOnBindDestroyEvent).toHaveBeenCalledWith(destroyInput)
112
+
113
+ expect(destroyInput.addEventListener).toHaveBeenCalledTimes(1)
114
+ expect(destroyInput.addEventListener).toHaveBeenCalledWith(
115
+ 'change',
116
+ expect.any(Function)
117
+ )
118
+ })
119
+
120
+ test('bindRemoveEvent called with element and click event added', () => {
121
+ const spyOnBindRemoveEvent = jest.spyOn(
122
+ HasManyClass.prototype,
123
+ '_bindRemoveEvent'
124
+ )
125
+ const removeLinks = document.querySelector(options.removeLink)
126
+ removeLinks.addEventListener = jest.fn()
127
+
128
+ new HasManyClass(element) // eslint-disable-line no-new
129
+
130
+ expect(spyOnBindRemoveEvent).toHaveBeenCalledTimes(1)
131
+ expect(spyOnBindRemoveEvent).toHaveBeenCalledWith(removeLinks)
132
+
133
+ expect(removeLinks.addEventListener).toHaveBeenCalledTimes(1)
134
+ expect(removeLinks.addEventListener).toHaveBeenCalledWith(
135
+ 'click',
136
+ expect.any(Function)
137
+ )
138
+ })
139
+
140
+ test('bindRemoveEventCallBack', () => {
141
+ const mockEvent = {
142
+ preventDefault: () => {},
143
+ target: document.body.querySelector('fieldset')
144
+ }
145
+ const spyOnRecomputePosition = jest.spyOn(
146
+ HasManyClass.prototype,
147
+ '_recomputePosition'
148
+ )
149
+ const HasMany = new HasManyClass(element)
150
+ const spyOnDispatch = jest.spyOn(HasMany.element, 'dispatchEvent')
151
+
152
+ const fieldSet = mockEvent.target.closest(HasMany.options.item)
153
+ fieldSet.remove = jest.fn()
154
+
155
+ const parent = HasMany.element
156
+ const removeBeforeEvent = HasMany.events.removeBefore
157
+ removeBeforeEvent.detail = { fieldSet, parent }
158
+ const removeAfterEvent = HasMany.events.removeAfter
159
+ removeAfterEvent.detail = { fieldSet, parent }
160
+
161
+ const output = HasMany._bindRemoveEventCallBack(mockEvent)
162
+
163
+ expect(spyOnRecomputePosition).toHaveBeenCalledTimes(1)
164
+ expect(spyOnDispatch).toHaveBeenCalledTimes(2)
165
+ expect(spyOnDispatch.mock.calls).toMatchObject([
166
+ [removeBeforeEvent],
167
+ [removeAfterEvent]
168
+ ])
169
+ expect(fieldSet.remove).toHaveBeenCalledTimes(1)
170
+ expect(output).toBeTruthy()
171
+ })
172
+
173
+ test('bindAddEvent called with correctly element and click event added', () => {
174
+ const spyOnBindAddEvent = jest.spyOn(
175
+ HasManyClass.prototype,
176
+ '_bindAddEvent'
177
+ )
178
+
179
+ const addLinks = document.querySelector(options.addLink)
180
+ addLinks.addEventListener = jest.fn()
181
+
182
+ new HasManyClass(element) // eslint-disable-line no-new
183
+
184
+ expect(spyOnBindAddEvent).toHaveBeenCalledTimes(1)
185
+ expect(spyOnBindAddEvent).toHaveBeenCalledWith(addLinks)
186
+
187
+ expect(addLinks.addEventListener).toHaveBeenCalledTimes(1)
188
+ expect(addLinks.addEventListener).toHaveBeenCalledWith(
189
+ 'click',
190
+ expect.any(Function)
191
+ )
192
+ })
193
+
194
+ test('bindAddEventCallBack', () => {
195
+ const mockEvent = {
196
+ preventDefault: () => {},
197
+ target: document.body.querySelector(options.addLink)
198
+ }
199
+ const HasMany = new HasManyClass(element)
200
+ const spyOnDispatchEvent = jest.spyOn(HasMany.element, 'dispatchEvent')
201
+ const $list = HasMany.element.querySelector(HasMany.options.list)
202
+ const spyOnListAppendChild = jest.spyOn($list, 'appendChild')
203
+ const spyOnBindEvents = jest.spyOn(HasMany, '_bindEvents')
204
+ const spyOnRecomputePosition = jest.spyOn(HasMany, '_recomputePosition')
205
+ const beforeAdd = HasMany.events.addBefore
206
+ const parent = HasMany.element
207
+ const datasetHTML = document.createElement('div')
208
+
209
+ const output = HasMany._bindAddEventCallBack(mockEvent)
210
+
211
+ expect(spyOnDispatchEvent).toHaveBeenCalledTimes(2)
212
+ expect(spyOnDispatchEvent.mock.calls).toMatchObject([
213
+ [beforeAdd, [parent]],
214
+ [HasMany.events.addAfter]
215
+ ])
216
+
217
+ expect(spyOnListAppendChild).toHaveBeenCalledTimes(1)
218
+ expect(spyOnListAppendChild).toHaveBeenCalledWith(datasetHTML)
219
+ expect(spyOnBindEvents).toHaveBeenCalledTimes(1)
220
+ expect(spyOnBindEvents).toHaveBeenCalledWith(datasetHTML)
221
+ expect(spyOnRecomputePosition).toHaveBeenCalledTimes(1)
222
+ expect(output).toBeTruthy()
223
+ })
224
+ })
@@ -1,4 +1,3 @@
1
- /* eslint-env jest */
2
1
  import {
3
2
  hasTurbolinks,
4
3
  turbolinksVisit,
@@ -51,54 +51,57 @@ class HasMany {
51
51
  }
52
52
 
53
53
  _bindAddEvent (el) {
54
- el.addEventListener('click', (e) => {
55
- let beforeAdd
56
- const el = e.target
57
- const parent = this.element
54
+ el.addEventListener('click', (e) => this._bindAddEventCallBack(e))
55
+ }
58
56
 
59
- e.preventDefault()
60
- parent.dispatchEvent(beforeAdd = this.events.addBefore, [parent])
57
+ _bindAddEventCallBack (e) {
58
+ let beforeAdd
59
+ const el = e.target
60
+ const parent = this.element
61
+ e.preventDefault()
62
+ parent.dispatchEvent(beforeAdd = this.events.addBefore, [parent])
61
63
 
62
- if (!beforeAdd.defaultPrevented) {
63
- let index = parent.dataset.hasManyIndex || parent.querySelectorAll(this.options.item).length - 1
64
- parent.setAttribute('data-has-many-index', ++index)
64
+ if (!beforeAdd.defaultPrevented) {
65
+ let index = parent.dataset.hasManyIndex || parent.querySelectorAll(this.options.item).length - 1
66
+ parent.setAttribute('data-has-many-index', ++index)
65
67
 
66
- const regex = new RegExp(el.dataset.placeholder, 'g')
67
- const html = el.dataset.html.replaceAll(regex, index)
68
- const newNode = document.createElement('div')
69
- newNode.innerHTML = html
68
+ const regex = new RegExp(el.dataset.placeholder, 'g')
69
+ const html = el.dataset.html.replace(regex, index)
70
+ const newNode = document.createElement('div')
71
+ newNode.innerHTML = html
70
72
 
71
- const fieldset = newNode.firstElementChild
72
- const $list = this.element.querySelector(this.options.list)
73
+ const fieldset = newNode.firstElementChild
74
+ const $list = this.element.querySelector(this.options.list)
73
75
 
74
- $list.appendChild(fieldset)
75
- this._bindEvents(fieldset)
76
- this._recomputePosition()
76
+ $list.appendChild(fieldset)
77
+ this._bindEvents(fieldset)
78
+ this._recomputePosition()
77
79
 
78
- const addAfterEvent = this.events.addAfter
79
- addAfterEvent.detail = { fieldset, parent }
80
- return parent.dispatchEvent(this.events.addAfter)
81
- }
82
- })
80
+ const addAfterEvent = this.events.addAfter
81
+ addAfterEvent.detail = { fieldset, parent }
82
+ return parent.dispatchEvent(this.events.addAfter)
83
+ }
83
84
  }
84
85
 
85
86
  _bindRemoveEvent (el) {
86
- el.addEventListener('click', (e) => {
87
- const el = e.target
88
- const parent = this.element
89
- const fieldset = el.closest(this.options.item)
90
- const removeBeforeEvent = this.events.removeBefore
91
- const removeAfterEvent = this.events.removeAfter
92
-
93
- e.preventDefault()
94
- this._recomputePosition()
87
+ el.addEventListener('click', (e) => this._bindRemoveEventCallBack(e))
88
+ }
95
89
 
96
- removeBeforeEvent.detail = { fieldset, parent }
97
- removeAfterEvent.detail = { fieldset, parent }
98
- parent.dispatchEvent(removeBeforeEvent)
99
- fieldset.remove()
100
- return parent.dispatchEvent(removeAfterEvent)
101
- })
90
+ _bindRemoveEventCallBack (e) {
91
+ const el = e.target
92
+ const parent = this.element
93
+ const fieldset = el.closest(this.options.item)
94
+ const removeBeforeEvent = this.events.removeBefore
95
+ const removeAfterEvent = this.events.removeAfter
96
+
97
+ e.preventDefault()
98
+ this._recomputePosition()
99
+
100
+ removeBeforeEvent.detail = { fieldset, parent }
101
+ removeAfterEvent.detail = { fieldset, parent }
102
+ parent.dispatchEvent(removeBeforeEvent)
103
+ fieldset.remove()
104
+ return parent.dispatchEvent(removeAfterEvent)
102
105
  }
103
106
 
104
107
  _bindEvents (el) {
@@ -1,3 +1,3 @@
1
1
  module Adminterface
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -11,6 +11,10 @@ module Adminterface
11
11
  ActiveRecord::Migration.next_migration_number(next_migration_number)
12
12
  end
13
13
 
14
+ def install_action_text
15
+ rails_command "action_text:install"
16
+ end
17
+
14
18
  def create_initializer
15
19
  template "active_admin_comment_action_text.rb",
16
20
  "config/initializers/active_admin_comment_action_text.rb"
@@ -2,10 +2,10 @@ module Adminterface
2
2
  module Generators
3
3
  class ConfigsGenerator < Rails::Generators::Base
4
4
  desc "Copies config files into a directory for customization"
5
- argument :namespace, type: :string, default: "Admin"
6
-
7
5
  source_root File.expand_path("templates", __dir__)
8
6
 
7
+ argument :namespace, type: :string, default: "Admin"
8
+
9
9
  def copy
10
10
  directory Adminterface::Engine.root.join("lib/adminterface/fixtures"),
11
11
  "config/adminterface/#{namespace.underscore}/"
@@ -4,37 +4,35 @@ module Adminterface
4
4
  desc "Installs Adminterface and generates necessary files and migrations"
5
5
  source_root File.expand_path("templates", __dir__)
6
6
 
7
- def install_rails_addons
8
- invoke "action_text:install"
9
- end
7
+ class_option :skip_comments,
8
+ type: :boolean, default: false,
9
+ desc: "Skip installation of ActionText for comments"
10
+ class_option :skip_examples,
11
+ type: :boolean, default: false,
12
+ desc: "Skip adding sample admin files"
13
+ class_option :use_webpacker,
14
+ type: :boolean, default: true,
15
+ desc: "Install assets with webpacker"
16
+ class_option :version,
17
+ aliases: "-v", type: :string, default: Adminterface::VERSION,
18
+ desc: "Install with a specific version"
10
19
 
11
20
  def install_assets
12
- install_webpacker? do
13
- generate "adminterface:webpacker"
14
- end
15
- end
21
+ return unless options[:use_webpacker]
16
22
 
17
- def install_action_text_for_comments
18
- generate "adminterface:comments"
23
+ generate "adminterface:webpacker -v #{options[:version]}"
19
24
  end
20
25
 
21
- def add_samples
22
- template "dashboard.rb", "app/admin/dashboard.rb"
23
- end
26
+ def install_comments
27
+ return if options[:skip_comments]
24
28
 
25
- private
29
+ generate "adminterface:comments"
30
+ end
26
31
 
27
- def install_webpacker?
28
- return yield if defined?(Webpacker)
32
+ def add_examples
33
+ return if options[:skip_examples]
29
34
 
30
- puts Rainbow("Adminterface requires webpacker:").yellow
31
- if yes?("Install webpacker and continue?")
32
- gem "webpacker"
33
- rails_command "webpacker:install"
34
- yield
35
- else
36
- puts Rainbow("Installation aborted").red
37
- end
35
+ template "dashboard.rb", "app/admin/dashboard.rb"
38
36
  end
39
37
  end
40
38
  end
@@ -2,13 +2,13 @@ module Adminterface
2
2
  module Generators
3
3
  class ViewsGenerator < Rails::Generators::Base
4
4
  desc "Copies view templates for customization"
5
-
6
5
  source_root Adminterface::Engine.root.join("app/views")
7
6
 
8
7
  def copy
9
8
  directory "active_admin", "app/views/active_admin"
10
9
  directory "kaminari/active_admin", "app/views/kaminari/active_admin"
11
- directory "layouts/active_admin_logged_out.html.erb", "app/views/layouts/active_admin_logged_out.html.erb"
10
+ directory "layouts/active_admin_logged_out.html.erb",
11
+ "app/views/layouts/active_admin_logged_out.html.erb"
12
12
  end
13
13
  end
14
14
  end
@@ -2,11 +2,24 @@ module Adminterface
2
2
  module Generators
3
3
  class WebpackerGenerator < Rails::Generators::Base
4
4
  desc "Install Stylesheets and JavaScripts using Webpacker"
5
+ source_root File.expand_path("templates", __dir__)
6
+
5
7
  class_option :version,
6
8
  aliases: "-v", type: :string, default: Adminterface::VERSION,
7
9
  desc: "Install with a specific npm package version"
8
10
 
9
- source_root File.expand_path("templates", __dir__)
11
+ def install_webpacker
12
+ return if webpacker_installed?
13
+
14
+ puts Rainbow("Adminterface requires webpacker:").yellow
15
+ if yes?("Install webpacker and continue?")
16
+ gem "webpacker"
17
+ rails_command "webpacker:install"
18
+ else
19
+ puts Rainbow("Installation aborted").red
20
+ abort
21
+ end
22
+ end
10
23
 
11
24
  def install_packages
12
25
  insert_into_file "config/webpack/environment.js", after: /require\(('|")@rails\/webpacker\1\);?\n/ do
@@ -18,7 +31,7 @@ module Adminterface
18
31
  EOF
19
32
  end
20
33
 
21
- run "yarn add @cmdbrew/adminterface@v#{find_npm_version}"
34
+ run "yarn add @cmdbrew/adminterface@#{npm_version}"
22
35
  end
23
36
 
24
37
  def install_assets
@@ -44,11 +57,12 @@ module Adminterface
44
57
 
45
58
  private
46
59
 
47
- def find_npm_version
48
- version = options[:version]
49
- return version unless version.include?(".rc")
60
+ def webpacker_installed?
61
+ defined?(Webpacker)
62
+ end
50
63
 
51
- version.gsub(".rc", "-rc")
64
+ def npm_version
65
+ options[:version].gsub(".rc", "-rc")
52
66
  end
53
67
  end
54
68
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adminterface
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - I-Lung Lee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-15 00:00:00.000000000 Z
11
+ date: 2021-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeadmin
@@ -80,7 +80,8 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.18'
83
- description: An add-on that brings Bootstrap 5 and other goodies into ActiveAdmin.
83
+ description: A gem that brings Bootstrap 5, advanced customizability, and other goodies
84
+ into ActiveAdmin
84
85
  email:
85
86
  - ilung@hey.com
86
87
  executables: []
@@ -174,8 +175,12 @@ files:
174
175
  - app/javascript/adminterface/initializers/plugins/input_counter.js
175
176
  - app/javascript/adminterface/initializers/plugins/password_visibility.js
176
177
  - app/javascript/adminterface/initializers/plugins/tom_select.js
178
+ - app/javascript/adminterface/lib/__tests__/checkbox_toggler.html
179
+ - app/javascript/adminterface/lib/__tests__/checkbox_toggler.spec.js
177
180
  - app/javascript/adminterface/lib/__tests__/detached_dropdown.spec.js
178
181
  - app/javascript/adminterface/lib/__tests__/filters.spec.js
182
+ - app/javascript/adminterface/lib/__tests__/has_many.html
183
+ - app/javascript/adminterface/lib/__tests__/has_many.spec.js
179
184
  - app/javascript/adminterface/lib/__tests__/utils.spec.js
180
185
  - app/javascript/adminterface/lib/batch_actions.js
181
186
  - app/javascript/adminterface/lib/checkbox_toggler.js
@@ -377,5 +382,5 @@ requirements: []
377
382
  rubygems_version: 3.2.22
378
383
  signing_key:
379
384
  specification_version: 4
380
- summary: An add-on that brings Bootstrap 5 and other goodies into ActiveAdmin.
385
+ summary: Build functional and beautiful web apps faster with ActiveAdmin
381
386
  test_files: []