card-mod-list 0.14.1 → 0.15.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/assets/script/pointer_config.js.coffee +78 -0
  3. data/assets/script/pointer_list_editor.js.coffee +68 -0
  4. data/assets/style/list.scss +102 -0
  5. data/data/files/mod_list_script_asset_output/file.js +4 -0
  6. data/data/real.yml +41 -0
  7. data/data/test.yml +12 -0
  8. data/db/migrate_core_cards/20160804112340_add_pointer_cards.rb +1 -1
  9. data/locales/de.yml +2 -0
  10. data/locales/en.yml +5 -0
  11. data/set/abstract/{01_pointer → 01_list}/events.rb +36 -0
  12. data/set/abstract/{01_pointer → 01_list}/html_views/checkbox_input.haml +0 -0
  13. data/set/abstract/01_list/html_views/click_select_input.haml +9 -0
  14. data/set/abstract/{01_pointer → 01_list}/html_views/list_input.haml +4 -6
  15. data/set/abstract/{01_pointer → 01_list}/html_views/list_item.haml +4 -6
  16. data/set/abstract/{01_pointer → 01_list}/html_views/radio_input.haml +3 -3
  17. data/set/abstract/{01_pointer → 01_list}/html_views.rb +27 -29
  18. data/set/abstract/{01_pointer → 01_list}/options_api.rb +3 -1
  19. data/set/abstract/{01_pointer → 01_list}/other_views.rb +0 -6
  20. data/set/abstract/{01_pointer.rb → 01_list.rb} +9 -0
  21. data/set/abstract/{code_pointer.rb → codename_list.rb} +1 -1
  22. data/set/abstract/delist.rb +8 -0
  23. data/set/abstract/{id_pointer.rb → id_list.rb} +13 -0
  24. data/set/abstract/reference_list.rb +11 -0
  25. data/set/all/supports_content_options.rb +1 -9
  26. data/set/right/content_option_view.rb +2 -0
  27. data/set/right/content_options.rb +2 -0
  28. data/set/self/input_options.rb +1 -1
  29. data/set/type/_reference_list_input_row.haml +21 -0
  30. data/set/type/link_list.rb +1 -6
  31. data/set/type/link_list_input.haml +21 -0
  32. data/set/type/list.rb +1 -10
  33. data/set/type/nest_list.rb +1 -6
  34. data/set/type/nest_list_input.haml +21 -0
  35. data/set/type/pointer.rb +1 -10
  36. metadata +35 -26
  37. data/set/abstract/01_pointer/html_views/autocomplete_input.haml +0 -3
  38. data/set/abstract/01_pointer/html_views/filter/filter_items.haml +0 -28
  39. data/set/abstract/01_pointer/html_views/filter/filtered_list_input.haml +0 -16
  40. data/set/abstract/01_pointer/html_views/filter.rb +0 -63
  41. data/set/type/link_list/link_list_input.haml +0 -24
  42. data/set/type/nest_list/nest_list_input.haml +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 128513caa820067dcf1ec3e1cd0b389c530cb2be8c6c317aabf8827c2ba6ccb4
4
- data.tar.gz: 9673c5da3b5838ceabe9a135f7a227204179e1f4e3260c3a44d4eb2ad7d747dd
3
+ metadata.gz: 23da4460389f951c1ba59d9fb6aba4918c07232e6d1c227764c388ca41711798
4
+ data.tar.gz: 39a12d646527c2b2ae1c03366731d494854b8b07ecb5612f005276efef15289f
5
5
  SHA512:
6
- metadata.gz: 746ad6da3fe5424df83c2db9c244401a93b2d15e703928c9b3b82f433fe906c0a6d3a6581f38e7f40371befeeb4fc2c5ce3f8d6389c71ae4345550de9cdca00e
7
- data.tar.gz: 4bb16393c851b9f5db6e4f7bcbc488806a9fcf2c27e0ff3cc016dbc3cdc2a5ad43ee4298d250a3c7151930362c7b04901c40feac02ccbc4905dfa0a5aa77ad62
6
+ metadata.gz: 06ea0e1e7a40861a8d1e3c3bb434d843b17828109b99ec3e1c5fa3603ecb6d1eb4a7acc25340997d40cef510ee74abbfb6c801eb7f66273cb37b62e00a0f786b
7
+ data.tar.gz: 057e4dbc59eae6cee5d1bb414f4e6c2fbcbad06d5f91a620dfaca118897483ec4295a60f89244e89065990259b2651e60052a018fc73f91b4075d1849469f382
@@ -0,0 +1,78 @@
1
+ $.extend decko.editors.content,
2
+ 'select.pointer-select': ->
3
+ pointerContent @val()
4
+ 'select.pointer-multiselect': ->
5
+ pointerContent @val()
6
+ '.pointer-radio-list': ->
7
+ pointerContent @find('input:checked').val()
8
+ '.pointer-list-ul': ->
9
+ pointerContent @find('input').map( -> $(this).val() )
10
+ '.pointer-link-list-ul': ->
11
+ decko.linkListContent @find('.input-group')
12
+ '._nest-list-ul': ->
13
+ decko.nestListContent @find('.input-group')
14
+ '.pointer-checkbox-list': ->
15
+ pointerContent @find('input:checked').map( -> $(this).val() )
16
+ '.pointer-select-list': ->
17
+ pointerContent @find('.pointer-select select').map( -> $(this).val() )
18
+ '._filtered-list': ->
19
+ pointerContent @find('._filtered-list-item').map( -> $(this).data('cardName') )
20
+ '._pointer-list': ->
21
+ pointerContent @find('._pointer-item').map( -> $(this).val() )
22
+ '._click-select-editor': ->
23
+ pointerContent @find('._select-item.selected').map( ->
24
+ $($(this).find('[data-card-name]')[0]).data('cardName'))
25
+ '._click-multiselect-editor': ->
26
+ pointerContent @find('._select-item.selected').map( ->
27
+ $($(this).find('[data-card-name]')[0]).data('cardName'))
28
+ # can't find evidence that the following is in use: #efm
29
+ # '.pointer-mixed': ->
30
+ # element = '.pointer-checkbox-sublist input:checked,\
31
+ # .pointer-sublist-ul input'
32
+ # pointerContent @find(element).map( -> $(this).val() )
33
+ # must happen after pointer-list-ul, I think
34
+ '.perm-editor': -> permissionsContent this
35
+
36
+ $.extend decko.editors.init,
37
+ ".pointer-list-editor": ->
38
+ @sortable handle: '.handle', cancel: ''
39
+ decko.initPointerList @find('input')
40
+
41
+ "._filtered-list" : ->
42
+ @sortable handle: '._handle', cancel: ''
43
+
44
+ $.extend decko,
45
+ initPointerList: (input) ->
46
+ decko.initAutoCardPlete input
47
+
48
+ pointerContent: (vals) ->
49
+ $.makeArray(vals).join "\n"
50
+
51
+ linkListContent: (input_groups) ->
52
+ vals = input_groups.map( ->
53
+ v = $(this).find('input._reference').val()
54
+ title = $(this).find('input._title').val()
55
+ v += "|#{title}" if title.length > 0
56
+ v
57
+ )
58
+ list = $.map $.makeArray(vals), (v) -> if v then '[[' + v + ']]'
59
+ $.makeArray(list).join "\n"
60
+
61
+ nestListContent: (input_groups) ->
62
+ vals = input_groups.map( ->
63
+ v = $(this).find('input._reference').val()
64
+ options = $(this).find('input._nest-options').val()
65
+ v += "|#{options}" if options.length > 0
66
+ v
67
+ )
68
+ list = $.map $.makeArray(vals), (v) -> if v then '{{' + v + '}}'
69
+ $.makeArray(list).join "\n"
70
+
71
+ pointerContent = (vals) ->
72
+ decko.pointerContent vals
73
+
74
+ permissionsContent = (ed) ->
75
+ return '_left' if ed.find('#inherit').is(':checked')
76
+ groups = ed.find('.perm-group input:checked').map( -> $(this).val() )
77
+ indivs = ed.find('.perm-indiv input' ).map( -> $(this).val() )
78
+ pointerContent $.makeArray(groups).concat($.makeArray(indivs))
@@ -0,0 +1,68 @@
1
+ $(window).ready ->
2
+ # add pointer item when clicking on "add another" button
3
+ $('body').on 'click', '._pointer-item-add', (event)->
4
+ decko.addPointerItem this
5
+ event.preventDefault() # Prevent link from following its href
6
+
7
+ # add pointer item when you hit enter in an item
8
+ $('body').on 'keydown', '.pointer-item-text', (event)->
9
+ if event.key == 'Enter'
10
+ decko.addPointerItem this
11
+ event.preventDefault() # was triggering extra item in unrelated pointer
12
+
13
+ # enable/disable add
14
+ $('body').on 'keyup', '.pointer-item-text', (_event)->
15
+ decko.updateAddItemButton this
16
+
17
+ $('body').on 'click', '.pointer-item-delete', ->
18
+ item = $(this).closest 'li'
19
+ list = item.closest('ul')
20
+ if list.find('.pointer-li').length > 1
21
+ item.remove()
22
+ else
23
+ item.find('input').val ''
24
+ decko.updateAddItemButton(list)
25
+
26
+ decko.slot.ready (slot) ->
27
+ slot.find('.pointer-list-editor').each ->
28
+ decko.updateAddItemButton this
29
+
30
+ $.extend decko,
31
+ addPointerItem: (el) ->
32
+ slot = $(el).slot()
33
+ slot.trigger "decko.slot.destroy"
34
+ # why is this necessary?
35
+ # this can have a lot of side effects in a multi-card form.
36
+
37
+ newInput = decko.nextPointerInput decko.lastPointerItem(el)
38
+ newInput.val ''
39
+
40
+ slot.trigger "decko.slot.ready"
41
+ decko.initializeEditors slot
42
+ # should be (but is not) handled by decko.slot.ready
43
+ # without this, "add another" was breaking tinymce editors in same slot
44
+
45
+ newInput.first().focus()
46
+ decko.updateAddItemButton el
47
+ decko.initPointerList newInput
48
+
49
+ nextPointerInput: (lastItem)->
50
+ lastInputs = lastItem.find 'input'
51
+ all_empty = true
52
+ for input in lastInputs
53
+ all_empty = all_empty and $(input).val() == ''
54
+ return lastInputs if all_empty
55
+
56
+ newItem = lastItem.clone()
57
+ lastItem.after newItem
58
+ newItem.attr("data-index", parseInt(lastItem.attr("data-index") + 1))
59
+ newItem.trigger "decko.item.added"
60
+ newItem.find 'input'
61
+
62
+ lastPointerItem: (el)->
63
+ $(el).closest('.content-editor').find '.pointer-li:last'
64
+
65
+ updateAddItemButton: (el)->
66
+ button = $(el).closest('.content-editor').find '._pointer-item-add'
67
+ disabled = decko.lastPointerItem(el).find('input').val() == ''
68
+ button.prop 'disabled', disabled
@@ -0,0 +1,102 @@
1
+ ul.pointer {
2
+ margin: 0;
3
+ padding: 0;
4
+ }
5
+
6
+ .pointer-list-editor {
7
+ margin: 0px;
8
+ padding: 0px;
9
+ li {
10
+ list-style: none;
11
+ white-space: nowrap;
12
+ }
13
+
14
+ .input-group-btn {
15
+ .btn {
16
+ border-bottom-width: 1px;
17
+ }
18
+ }
19
+
20
+ ._pointer-item-add {
21
+ margin-top: 10px;
22
+ }
23
+
24
+ .input-group-addon.handle {
25
+ padding-left: 1px;
26
+ padding-right: 1px;
27
+
28
+ .glyphicon-option-vertical.left {
29
+ margin-right: -7px;
30
+ }
31
+ .glyphicon-option-vertical.right {
32
+ margin-left: -7px;
33
+ }
34
+ }
35
+ }
36
+
37
+ /*---------- ( Link list )-------------*/
38
+
39
+ .group-text-middle {
40
+ border-left: none;
41
+ border-right: none;
42
+ }
43
+
44
+ .group-text-left {
45
+ border-right: none;
46
+ }
47
+
48
+ .group-text-right {
49
+ border-left: none;
50
+ }
51
+
52
+
53
+ /*---------- ( Nest list editor )-------------*/
54
+
55
+ .input-nest-syntax {
56
+ background-color: $input-bg;
57
+ font-weight: 200;
58
+ font-size: 1.3rem;
59
+ padding-top: 0.1rem;
60
+ padding-bottom: 0.1rem;
61
+ }
62
+
63
+ .nest-form-header .input-group {
64
+ height: 2rem;
65
+
66
+ .input-group-text {
67
+ visibility: hidden;
68
+ }
69
+ }
70
+
71
+ .one-line-pointer-list {
72
+ div.pointer-item {
73
+ display: inline-block
74
+ }
75
+ }
76
+
77
+ /*-------------------- */
78
+ /*- SEARCH & POINTER - */
79
+ /*-------------------- */
80
+
81
+ // TODO: use shared class for pointers and searches
82
+
83
+ div.pointer-list,
84
+ div.search-result-list {
85
+ list-style: none;
86
+ padding: 5px 0;
87
+ margin: 0;
88
+ .closed-view > .d0-card-frame {
89
+ margin-bottom: 0;
90
+ }
91
+ }
92
+ div.pointer-list .pointer-item,
93
+ div.search-result-list .search-result-item {
94
+ margin: 0;
95
+ }
96
+ .search-result-item.item-link,
97
+ .pointer-item.item-link,
98
+ .search-result-item.item-name,
99
+ .pointer-item.item-name {
100
+ text-indent: -15px;
101
+ padding-left: 15px;
102
+ }
@@ -0,0 +1,4 @@
1
+ // pointer_config.js.coffee
2
+ (function(){var t,n;$.extend(decko.editors.content,{"select.pointer-select":function(){return n(this.val())},"select.pointer-multiselect":function(){return n(this.val())},".pointer-radio-list":function(){return n(this.find("input:checked").val())},".pointer-list-ul":function(){return n(this.find("input").map(function(){return $(this).val()}))},".pointer-link-list-ul":function(){return decko.linkListContent(this.find(".input-group"))},"._nest-list-ul":function(){return decko.nestListContent(this.find(".input-group"))},".pointer-checkbox-list":function(){return n(this.find("input:checked").map(function(){return $(this).val()}))},".pointer-select-list":function(){return n(this.find(".pointer-select select").map(function(){return $(this).val()}))},"._filtered-list":function(){return n(this.find("._filtered-list-item").map(function(){return $(this).data("cardName")}))},"._pointer-list":function(){return n(this.find("._pointer-item").map(function(){return $(this).val()}))},"._click-select-editor":function(){return n(this.find("._select-item.selected").map(function(){return $($(this).find("[data-card-name]")[0]).data("cardName")}))},"._click-multiselect-editor":function(){return n(this.find("._select-item.selected").map(function(){return $($(this).find("[data-card-name]")[0]).data("cardName")}))},".perm-editor":function(){return t(this)}}),$.extend(decko.editors.init,{".pointer-list-editor":function(){return this.sortable({handle:".handle",cancel:""}),decko.initPointerList(this.find("input"))},"._filtered-list":function(){return this.sortable({handle:"._handle",cancel:""})}}),$.extend(decko,{initPointerList:function(t){return decko.initAutoCardPlete(t)},pointerContent:function(t){return $.makeArray(t).join("\n")},linkListContent:function(t){var n,i;return i=t.map(function(){var t,n;return n=$(this).find("input._reference").val(),(t=$(this).find("input._title").val()).length>0&&(n+="|"+t),n}),n=$.map($.makeArray(i),function(t){if(t)return"[["+t+"]]"}),$.makeArray(n).join("\n")},nestListContent:function(t){var n,i;return i=t.map(function(){var t,n;return n=$(this).find("input._reference").val(),(t=$(this).find("input._nest-options").val()).length>0&&(n+="|"+t),n}),n=$.map($.makeArray(i),function(t){if(t)return"{{"+t+"}}"}),$.makeArray(n).join("\n")}}),n=function(t){return decko.pointerContent(t)},t=function(t){var i,e;return t.find("#inherit").is(":checked")?"_left":(i=t.find(".perm-group input:checked").map(function(){return $(this).val()}),e=t.find(".perm-indiv input").map(function(){return $(this).val()}),n($.makeArray(i).concat($.makeArray(e))))}}).call(this);
3
+ // pointer_list_editor.js.coffee
4
+ (function(){$(window).ready(function(){return $("body").on("click","._pointer-item-add",function(t){return decko.addPointerItem(this),t.preventDefault()}),$("body").on("keydown",".pointer-item-text",function(t){if("Enter"===t.key)return decko.addPointerItem(this),t.preventDefault()}),$("body").on("keyup",".pointer-item-text",function(){return decko.updateAddItemButton(this)}),$("body").on("click",".pointer-item-delete",function(){var t,e;return(e=(t=$(this).closest("li")).closest("ul")).find(".pointer-li").length>1?t.remove():t.find("input").val(""),decko.updateAddItemButton(e)})}),decko.slot.ready(function(t){return t.find(".pointer-list-editor").each(function(){return decko.updateAddItemButton(this)})}),$.extend(decko,{addPointerItem:function(t){var e,n;return(n=$(t).slot()).trigger("decko.slot.destroy"),(e=decko.nextPointerInput(decko.lastPointerItem(t))).val(""),n.trigger("decko.slot.ready"),decko.initializeEditors(n),e.first().focus(),decko.updateAddItemButton(t),decko.initPointerList(e)},nextPointerInput:function(t){var e,n,o,i,d,r;for(e=!0,n=0,d=(i=t.find("input")).length;n<d;n++)o=i[n],e=e&&""===$(o).val();return e?i:(r=t.clone(),t.after(r),r.attr("data-index",parseInt(t.attr("data-index")+1)),r.trigger("decko.item.added"),r.find("input"))},lastPointerItem:function(t){return $(t).closest(".content-editor").find(".pointer-li:last")},updateAddItemButton:function(t){var e,n;return e=$(t).closest(".content-editor").find("._pointer-item-add"),n=""===decko.lastPointerItem(t).find("input").val(),e.prop("disabled",n)}})}).call(this);
data/data/real.yml ADDED
@@ -0,0 +1,41 @@
1
+ ---
2
+ - :name: List
3
+ :type: :cardtype
4
+ :codename: list
5
+ :fields:
6
+ :description: |-
7
+ <p>Lists list one or more other cards. [[http://decko.org/:list|more]]</p>
8
+
9
+ - :name: Pointer
10
+ :type: :cardtype
11
+ :codename: pointer
12
+ :fields:
13
+ :description: |-
14
+ <p>Pointers point another card. [[http://decko.org/:pointer|more]]</p>
15
+
16
+ - :name: Nest list
17
+ :type: :cardtype
18
+ :codename: nest_list
19
+ - :name: Link list
20
+ :type: :cardtype
21
+ :codename: link_list
22
+
23
+ - :name: "*content options"
24
+ :type: :setting
25
+ :codename: content_options
26
+
27
+ - :name: "*content option view"
28
+ :type: :setting
29
+ :codename: content_option_view
30
+
31
+ - :name:
32
+ - :structure
33
+ - :right
34
+ - :default
35
+ :type: :nest_list
36
+
37
+ - :name:
38
+ - :all
39
+ - :content_option_view
40
+ :type: :phrase
41
+ :content: smart_label
data/data/test.yml ADDED
@@ -0,0 +1,12 @@
1
+ ---
2
+ - :name:
3
+ - friends
4
+ - :right
5
+ - :default
6
+ :type: :list
7
+ - :name:
8
+ - friends
9
+ - :right
10
+ - :content_options
11
+ :type: :search_type
12
+ :content: '{"type":"User"}'
@@ -2,7 +2,7 @@
2
2
 
3
3
  class AddPointerCards < Cardio::Migration
4
4
  def up
5
- ensure_card name: "script: pointer config",
5
+ Card.ensure name: "script: pointer config",
6
6
  type_id: Card::CoffeeScriptID,
7
7
  codename: "script_pointer_config"
8
8
  end
data/locales/de.yml ADDED
@@ -0,0 +1,2 @@
1
+ de:
2
+ list_invalid_item_type: "%{item} hat einen ungültigen Typ (%{type)). Nur %{allowed_types} sind erlaubt."
data/locales/en.yml ADDED
@@ -0,0 +1,5 @@
1
+ en:
2
+ list_invalid_item_type:
3
+ "%{item} has an invalid type: %{type}. Allowed types: %{allowed_types}."
4
+ list_duplicate_items_names:
5
+ "duplicate item names: %{duplicates}"
@@ -10,6 +10,42 @@ event :insert_item_event, :prepare_to_validate, on: :save, when: :item_to_insert
10
10
  insert_item index.to_i, item_to_insert
11
11
  end
12
12
 
13
+ event :validate_item_type, :validate, on: :save, when: :validate_item_type? do
14
+ ok_types = Array.wrap(ok_item_types).map(&:codename)
15
+ item_cards.each do |item|
16
+ next if ok_types.include? item.type_code
17
+
18
+ errors.add :content, t(:list_invalid_item_type,
19
+ item: item.name,
20
+ type: item.type,
21
+ allowed_types: ok_types.map(&:cardname).to_sentence)
22
+ end
23
+ end
24
+
25
+ event :validate_item_uniqueness, :validate, on: :save, when: :unique_items? do
26
+ return unless (dupes = duplicate_item_names)&.present?
27
+
28
+ errors.add :content, t(:list_duplicate_items_names, duplicates: dupes.to_sentence)
29
+ end
30
+
31
+ def duplicate_item_names
32
+ names = item_names
33
+ names.uniq.select { |n| names.count(n) > 1 }
34
+ end
35
+
36
+ # for override
37
+ def unique_items?
38
+ false
39
+ end
40
+
41
+ def ok_item_types
42
+ nil
43
+ end
44
+
45
+ def validate_item_type?
46
+ ok_item_types.present?
47
+ end
48
+
13
49
  def item_to_insert
14
50
  Env.params["insert_item"]
15
51
  end
@@ -0,0 +1,9 @@
1
+ .d-flex.flex-wrap{class:"_click-#{multiselect ? 'multi' : ''}select-editor"}
2
+ - options_hash = card.options_hash
3
+ - if options_hash.empty?
4
+ no options
5
+ - else
6
+ - options_hash.each do |option_label, option_name|
7
+ - checked = option_name == card.first_name
8
+ ._select-item{class: checked && "selected"}
9
+ = nest option_name, view: item_view
@@ -2,14 +2,12 @@
2
2
  - items.each do |item|
3
3
  %li.pointer-li.mb-1
4
4
  %span.input-group
5
- .input-group-prepend.handle
6
- %span.input-group-text
7
- = icon_tag :reorder
5
+ %span.input-group-text.handle
6
+ = icon_tag :reorder
8
7
  = text_field_tag "pointer_item", item, class: "pointer-item-text form-control",
9
8
  "data-options-card": options_card
10
- .input-group-append
11
- %button.pointer-item-delete.btn.btn-secondary{ type: "button"}
12
- = icon_tag :remove
9
+ %button.pointer-item-delete.btn.btn-secondary{ type: "button"}
10
+ = icon_tag :remove
13
11
  %span.input-group
14
12
  %button._pointer-item-add.btn.btn-secondary{ type: "submit" }
15
13
  = icon_tag "add"
@@ -2,11 +2,9 @@
2
2
 
3
3
  %li.pointer-li.mb-1
4
4
  %span.input-group
5
- .input-group-prepend.handle
6
- %span.input-group-text
7
- = icon_tag :reorder
5
+ %span.input-group-text.handle
6
+ = icon_tag :reorder
8
7
  = text_field_tag "pointer_item", item, class: "pointer-item-text form-control",
9
8
  "data-options-card": options_card
10
- .input-group-append
11
- %button.pointer-item-delete.btn.btn-secondary{:type => "button"}
12
- = icon_tag :remove
9
+ %button.pointer-item-delete.btn.btn-secondary{:type => "button"}
10
+ = icon_tag :remove
@@ -5,9 +5,9 @@
5
5
  - else
6
6
  - input_name = "pointer_radio_button-#{card.key}"
7
7
  - options_hash.each do |option_label, option_name|
8
- - checked = option_name == card.item_name
8
+ - checked = option_name == card.first_name
9
9
  - id = "pointer-radio-#{option_name.to_name.key}"
10
- %li.pointer-radio.radio
10
+ %li.pointer-radio.radio.form-check
11
11
  = radio_button_tag input_name, option_name, checked,
12
- id: id, class: "pointer-radio-button #{'_submit-on-change' if submit_on_change}"
12
+ id: id, class: "pointer-radio-button form-check-input #{'_submit-on-change' if submit_on_change}"
13
13
  = option_label option_label, id
@@ -3,15 +3,12 @@ format :html do
3
3
  standard_pointer_core
4
4
  end
5
5
 
6
- view :item_cores, cache: :never do
7
- card.known_item_cards.map do |item|
8
- nest item, view: :core
9
- end.join "\n"
10
- end
11
-
12
6
  def standard_pointer_core
13
7
  with_paging do |paging_args|
14
- wrap_with :div, standard_pointer_items(paging_args), class: "pointer-list"
8
+ wrap_with :div,
9
+ class: "card-list card-list-#{item_view_options[:view]} pointer-list" do
10
+ standard_pointer_items(paging_args)
11
+ end
15
12
  end
16
13
  end
17
14
 
@@ -20,8 +17,8 @@ format :html do
20
17
  end
21
18
 
22
19
  view :one_line_content do
23
- item_view = implicit_item_view
24
- item_view = item_view == "name" ? "name" : "link"
20
+ item_view = implicit_item_view&.to_sym
21
+ item_view = item_view == :name ? :name : :link
25
22
  wrap_with :div, class: "pointer-list one-line-pointer-list" do
26
23
  # limit to first 10 items to optimize
27
24
  pointer_items(view: item_view, limit: 10, offset: 0).join ", "
@@ -36,6 +33,10 @@ format :html do
36
33
  _render_hidden_content_field + super()
37
34
  end
38
35
 
36
+ def hidden_input
37
+ ""
38
+ end
39
+
39
40
  def default_input_type
40
41
  :list
41
42
  end
@@ -57,7 +58,7 @@ format :html do
57
58
  end
58
59
 
59
60
  def autocomplete_input
60
- haml :autocomplete_input, item: items_for_input.first, options_card: options_card_name
61
+ autocomplete_field items_for_input.first, options_card_name
61
62
  end
62
63
 
63
64
  def checkbox_input
@@ -71,34 +72,31 @@ format :html do
71
72
  def select_input
72
73
  options = { "-- Select --" => "" }.merge card.options_hash
73
74
  select_tag("pointer_select-#{unique_id}",
74
- options_for_select(options, card.item_name),
75
+ options_for_select(options, card.first_name),
75
76
  class: "pointer-select form-control")
76
77
  end
77
78
 
78
- def multiselect_input
79
- select_tag "pointer_multiselect-#{unique_id}",
80
- options_for_select(card.options_hash, card.item_names),
81
- multiple: true, class: "pointer-multiselect form-control"
79
+ def bar_select_input
80
+ raw haml(:click_select_input, item_view: :bar, multiselect: false)
82
81
  end
83
82
 
84
- def add_item_modal_link
85
- modal_link "Add Item",
86
- size: :large,
87
- class: "btn btn-sm btn-outline-secondary _add-item-link",
88
- path: { view: :filter_items_modal,
89
- item: implicit_item_view,
90
- filter_card: filter_card.name,
91
- slot_selector: filtered_list_slot_class,
92
- item_selector: "_filtered-list-item",
93
- slot: { hide: [:modal_footer] },
94
- filter: { not_ids: not_ids_value } }
83
+ def box_select_input
84
+ raw haml(:click_select_input, item_view: :box, multiselect: false)
95
85
  end
96
86
 
97
- def not_ids_value
98
- card.item_ids.map(&:to_s).join(",")
87
+ def bar_multiselect_input
88
+ raw haml(:click_select_input, item_view: :bar, multiselect: true)
99
89
  end
100
90
 
101
- def add_item_overlay_link; end
91
+ def box_multiselect_input
92
+ raw haml(:click_select_input, item_view: :box, multiselect: true)
93
+ end
94
+
95
+ def multiselect_input
96
+ select_tag "pointer_multiselect-#{unique_id}",
97
+ options_for_select(card.options_hash, card.item_names),
98
+ multiple: true, class: "pointer-multiselect form-control"
99
+ end
102
100
 
103
101
  def one_line_content
104
102
  if count == 1
@@ -68,7 +68,9 @@ end
68
68
 
69
69
  format :html do
70
70
  def option_label option_name, id
71
- %(<label for="#{id}">#{option_label_text option_name}</label>)
71
+ wrap_with :label, class: "form-check-label", for: id do
72
+ option_label_text option_name
73
+ end
72
74
  end
73
75
 
74
76
  def option_view
@@ -5,12 +5,6 @@ format do
5
5
  20
6
6
  end
7
7
 
8
- def item_links args={}
9
- card.item_cards(args).map do |item_card|
10
- subformat(item_card).render_link
11
- end
12
- end
13
-
14
8
  def nest_item_array
15
9
  card.item_cards.map do |item|
16
10
  nest_item item
@@ -28,6 +28,15 @@ def swap_names old_name, new_name
28
28
  end
29
29
  end
30
30
 
31
+ format :html do
32
+ view :view_list do
33
+ %i[bar box closed titled labeled].map do |view|
34
+ voo.items[:view] = view
35
+ wrap_with :p, [content_tag(:h3, "#{view} items"), render_content]
36
+ end
37
+ end
38
+ end
39
+
31
40
  private
32
41
 
33
42
  def chunk_class
@@ -1,4 +1,4 @@
1
- include_set Abstract::Pointer
1
+ include_set Abstract::List
2
2
 
3
3
  def self.included host_class
4
4
  basket[host_class.basket_name] = []
@@ -0,0 +1,8 @@
1
+ # remove set members from all lists that reference them upon deletion
2
+ event :delist, :prepare_to_store, on: :delete do
3
+ referers.each do |referer|
4
+ next unless referer.is_a? Abstract::List
5
+ referer.drop_item name
6
+ subcard referer
7
+ end
8
+ end
@@ -23,3 +23,16 @@ end
23
23
  def export_content
24
24
  item_names.join "\n"
25
25
  end
26
+
27
+ def replace_references _old_name, _new_name
28
+ # noop
29
+ end
30
+
31
+ # override reference creation so there are no referee_keys
32
+ # (referee_keys can screw things up for these cards when things get renamed)
33
+ def create_references_out
34
+ referee_ids = item_ids
35
+ return if referee_ids.empty?
36
+
37
+ Reference.mass_insert(referee_ids.map { |rid| [id, rid, "null", "'L'"] })
38
+ end
@@ -0,0 +1,11 @@
1
+ format do
2
+ def chunk_list
3
+ :references
4
+ end
5
+ end
6
+
7
+ format :html do
8
+ def header_field text
9
+ content_tag :span, text, class: "form-control border-0 control-label"
10
+ end
11
+ end
@@ -1,12 +1,4 @@
1
- basket[:list_input_options] = [
2
- "radio",
3
- "checkbox",
4
- "select",
5
- "multiselect",
6
- "list",
7
- "filtered list",
8
- "autocomplete"
9
- ]
1
+ basket[:list_input_options] = %w[radio checkbox select multiselect list autocomplete]
10
2
 
11
3
  def supports_content_options?
12
4
  false
@@ -1,3 +1,5 @@
1
+ assign_type :phrase
2
+
1
3
  format :html do
2
4
  def quick_edit
3
5
  if card.left.prototype_default_card&.try(:show_content_options?) &&
@@ -1,3 +1,5 @@
1
+ assign_type :list
2
+
1
3
  def default_limit
2
4
  cql_limit = fetch_query.limit if respond_to?(:fetch_query)
3
5
  cql_limit || 50
@@ -1,4 +1,4 @@
1
- include_set Abstract::Pointer
1
+ include_set Abstract::List
2
2
 
3
3
  def content
4
4
  basket[:list_input_options].to_pointer_content
@@ -0,0 +1,21 @@
1
+ %div.input-group
2
+ %span.input-group-text.handle
3
+ = icon_tag :reorder
4
+ %span.input-group-text.text-muted.group-text-left.input-nest-syntax
5
+ = prefix
6
+ = name_field
7
+ %span.input-group-text.text-muted.group-text-middle.input-nest-syntax
8
+ = "|"
9
+ = options_field
10
+ %span.input-group-text.text-muted.group-text-end.input-nest-syntax
11
+ = postfix
12
+ .input-group-append
13
+ %span.input-group-text.p-0
14
+ %button.pointer-item-delete.btn.btn-secondary{ type: "button"}
15
+ = icon_tag :remove
16
+ - if show_edit_button
17
+ .input-group-append.ml-2
18
+ %span.input-group-text.p-0.border-primary
19
+ %button.btn.btn-primary._open-nest-editor{ type: "button"}
20
+ = icon_tag :edit
21
+
@@ -1,4 +1,5 @@
1
1
  include_set Abstract::Items
2
+ include_set Abstract::ReferenceList
2
3
 
3
4
  def raw_item_strings content
4
5
  reference_chunks(content).map(&:referee_name)
@@ -10,12 +11,6 @@ def item_titles default_to_name=true
10
11
  end
11
12
  end
12
13
 
13
- format do
14
- def chunk_list
15
- :references
16
- end
17
- end
18
-
19
14
  format :html do
20
15
  def input_type
21
16
  :link_list
@@ -0,0 +1,21 @@
1
+ .nest-form-header
2
+ = haml_partial :reference_list_input_row,
3
+ name_field: header_field("Link target"),
4
+ options_field: header_field("Link text"),
5
+ postfix: "}}",
6
+ prefix: "{{",
7
+ show_edit_button: false
8
+ %ul{class: ul_classes}
9
+ - items.each.with_index do |(reference, title), index|
10
+ %li.pointer-li.mb-2._nest-form{"data-index": index}
11
+ = haml_partial :reference_list_input_row,
12
+ name_field: name_autocomplete_field(reference, "_reference pointer-item-text form-control group-text-middle"),
13
+ options_field: text_field_tag("pointer_item_title", title, class: "_title pointer-item-text form-control group-text-middle"),
14
+ prefix: "[[",
15
+ postfix: "]]",
16
+ show_edit_button: false
17
+
18
+ %span.input-group
19
+ %button._pointer-item-add.btn.btn-secondary{ type: "submit" }
20
+ = icon_tag "add"
21
+ add another
data/set/type/list.rb CHANGED
@@ -1,10 +1 @@
1
- include_set Abstract::Pointer
2
-
3
- format :html do
4
- view :view_list do
5
- %i[info_bar bar box closed titled labeled].map do |view|
6
- voo.items[:view] = view
7
- wrap_with :p, [content_tag(:h3, "#{view} items"), render_content]
8
- end
9
- end
10
- end
1
+ include_set Abstract::List
@@ -1,4 +1,5 @@
1
1
  include_set Abstract::Items
2
+ include_set Abstract::ReferenceList
2
3
 
3
4
  def raw_item_strings content
4
5
  reference_chunks(content).map(&:referee_raw_name)
@@ -12,12 +13,6 @@ def items_content array
12
13
  standardized_items(array).join "\n"
13
14
  end
14
15
 
15
- format do
16
- def chunk_list
17
- :references
18
- end
19
- end
20
-
21
16
  format :html do
22
17
  def input_type
23
18
  :nest_list
@@ -0,0 +1,21 @@
1
+ .nest-form-header
2
+ = haml_partial :reference_list_input_row,
3
+ name_field: header_field("Name"),
4
+ options_field: header_field("Options"),
5
+ postfix: "}}",
6
+ prefix: "{{",
7
+ show_edit_button: false
8
+ %ul{class: ul_classes}
9
+ - items.each.with_index do |(reference, title), index|
10
+ %li.pointer-li.mb-2._nest-form{"data-index": index}
11
+ = haml_partial :reference_list_input_row,
12
+ name_field: name_autocomplete_field(reference, "_reference pointer-item-text form-control group-text-middle"),
13
+ options_field: text_field_tag("pointer_item_title", title, class: "_title _nest-options pointer-item-text form-control group-text-middle"),
14
+ prefix: "{{",
15
+ postfix: "}}",
16
+ show_edit_button: true
17
+
18
+ %span.input-group
19
+ %button._pointer-item-add.btn.btn-secondary{ type: "submit" }
20
+ = icon_tag "add"
21
+ add another
data/set/type/pointer.rb CHANGED
@@ -1,10 +1 @@
1
- include_set Abstract::Pointer
2
-
3
- format :html do
4
- view :view_list do
5
- %i[info_bar bar box closed titled labeled].map do |view|
6
- voo.items[:view] = view
7
- wrap_with :p, [content_tag(:h3, "#{view} items"), render_content]
8
- end
9
- end
10
- end
1
+ include_set Abstract::List
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: card-mod-list
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.1
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan McCutchen
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-01-04 00:00:00.000000000 Z
13
+ date: 2023-01-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: card
@@ -18,42 +18,42 @@ dependencies:
18
18
  requirements:
19
19
  - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: 1.104.1
21
+ version: 1.105.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - '='
27
27
  - !ruby/object:Gem::Version
28
- version: 1.104.1
28
+ version: 1.105.0
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: card-mod-format
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
33
  - - '='
34
34
  - !ruby/object:Gem::Version
35
- version: 0.14.1
35
+ version: 0.15.0
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - '='
41
41
  - !ruby/object:Gem::Version
42
- version: 0.14.1
42
+ version: 0.15.0
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: card-mod-collection
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - '='
48
48
  - !ruby/object:Gem::Version
49
- version: 0.14.1
49
+ version: 0.15.0
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
54
  - - '='
55
55
  - !ruby/object:Gem::Version
56
- version: 0.14.1
56
+ version: 0.15.0
57
57
  description: ''
58
58
  email:
59
59
  - info@decko.org
@@ -61,34 +61,42 @@ executables: []
61
61
  extensions: []
62
62
  extra_rdoc_files: []
63
63
  files:
64
+ - assets/script/pointer_config.js.coffee
65
+ - assets/script/pointer_list_editor.js.coffee
66
+ - assets/style/list.scss
67
+ - data/files/mod_list_script_asset_output/file.js
68
+ - data/real.yml
69
+ - data/test.yml
64
70
  - db/migrate_core_cards/20160804112340_add_pointer_cards.rb
65
71
  - db/migrate_core_cards/20211107155032_out_with_the_brackets.rb
66
- - set/abstract/01_pointer.rb
67
- - set/abstract/01_pointer/events.rb
68
- - set/abstract/01_pointer/html_views.rb
69
- - set/abstract/01_pointer/html_views/autocomplete_input.haml
70
- - set/abstract/01_pointer/html_views/checkbox_input.haml
71
- - set/abstract/01_pointer/html_views/filter.rb
72
- - set/abstract/01_pointer/html_views/filter/filter_items.haml
73
- - set/abstract/01_pointer/html_views/filter/filtered_list_input.haml
74
- - set/abstract/01_pointer/html_views/list_input.haml
75
- - set/abstract/01_pointer/html_views/list_item.haml
76
- - set/abstract/01_pointer/html_views/radio_input.haml
77
- - set/abstract/01_pointer/options_api.rb
78
- - set/abstract/01_pointer/other_views.rb
79
- - set/abstract/code_pointer.rb
80
- - set/abstract/id_pointer.rb
72
+ - locales/de.yml
73
+ - locales/en.yml
74
+ - set/abstract/01_list.rb
75
+ - set/abstract/01_list/events.rb
76
+ - set/abstract/01_list/html_views.rb
77
+ - set/abstract/01_list/html_views/checkbox_input.haml
78
+ - set/abstract/01_list/html_views/click_select_input.haml
79
+ - set/abstract/01_list/html_views/list_input.haml
80
+ - set/abstract/01_list/html_views/list_item.haml
81
+ - set/abstract/01_list/html_views/radio_input.haml
82
+ - set/abstract/01_list/options_api.rb
83
+ - set/abstract/01_list/other_views.rb
84
+ - set/abstract/codename_list.rb
85
+ - set/abstract/delist.rb
86
+ - set/abstract/id_list.rb
87
+ - set/abstract/reference_list.rb
81
88
  - set/all/supports_content_options.rb
82
89
  - set/right/content_option_view.rb
83
90
  - set/right/content_options.rb
84
91
  - set/self/content_option_view.rb
85
92
  - set/self/content_options.rb
86
93
  - set/self/input_options.rb
94
+ - set/type/_reference_list_input_row.haml
87
95
  - set/type/link_list.rb
88
- - set/type/link_list/link_list_input.haml
96
+ - set/type/link_list_input.haml
89
97
  - set/type/list.rb
90
98
  - set/type/nest_list.rb
91
- - set/type/nest_list/nest_list_input.haml
99
+ - set/type/nest_list_input.haml
92
100
  - set/type/pointer.rb
93
101
  homepage: https://decko.org
94
102
  licenses:
@@ -100,6 +108,7 @@ metadata:
100
108
  wiki_uri: https://decko.org
101
109
  documentation_url: http://docs.decko.org/
102
110
  card-mod: list
111
+ card-mod-group: gem-defaults
103
112
  post_install_message:
104
113
  rdoc_options: []
105
114
  require_paths:
@@ -115,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
124
  - !ruby/object:Gem::Version
116
125
  version: '0'
117
126
  requirements: []
118
- rubygems_version: 3.2.15
127
+ rubygems_version: 3.3.11
119
128
  signing_key:
120
129
  specification_version: 4
121
130
  summary: list of cards
@@ -1,3 +0,0 @@
1
- .pointer-list-editor.pointer-list-ul
2
- = text_field_tag 'pointer_item', item, class: "pointer-item-text form-control",
3
- "data-options-card": options_card
@@ -1,28 +0,0 @@
1
- ._filter-items.container-fluid.nodblclick._noFilterUrlUpdates
2
- .row
3
- = nest filter_card, view: :filter_form
4
- ._unselected.col-6.border.mt-2.px-0
5
- = nest filter_card, view: :select_item,
6
- items: { view: implicit_item_view },
7
- cql: { limit: 10 }
8
- ._selected.col-6.border.mt-2.pl-0.pr-0
9
- .selected-box
10
- .card-header
11
- %h5
12
- Selected
13
- .badge.badge-secondary
14
- %span._selected-items 0
15
- ._selected-item-list{ style: "display:none" }
16
- .deselector.p-2
17
- %input#deselect-all._deselect-all{ type: "checkbox", checked: true, disabled: true }
18
- %label{ for: "deselect-all" }
19
- deselect
20
- %span._selected-items 0
21
- following
22
- ._selected-bin.p-2
23
- ._filter-help.alert.alert-secondary
24
- Filter and select items to add them here.
25
- .form-group
26
- .selected-item-buttons.p-2
27
- = button_tag "Cancel", class: "cancel-modal", data: { dismiss: :modal }
28
- = render :add_selected_link
@@ -1,16 +0,0 @@
1
- %div.filtered-list-editor
2
- %ul.pointer-list.filtered-list-review._pointer-filtered-list.list-group.vertical.p-0
3
- - card.item_cards(context: :raw).each do |item_card|
4
- %li._filtered-list-item.clearfix{ data: item_card.format.wrap_data(false) }
5
- %span._handle.float-left.m-2
6
- = icon_tag :reorder
7
- - nest_item item_card do |rendered, view|
8
- %span{ class: "item-#{view} float-left w-75"}
9
- = rendered
10
- %span.filtered-list-item-button
11
- %button._filtered-list-item-delete.btn.btn-secondary.btn-sm.m-2{:type => "button"}
12
- = icon_tag :remove
13
- %br
14
- .clearfix
15
- = add_item_modal_link
16
-
@@ -1,63 +0,0 @@
1
- format :html do
2
- view :filtered_list, unknown: true do
3
- filtered_list_input
4
- end
5
-
6
- view :filter_items_modal, unknown: true, wrap: :modal do
7
- render_filter_items
8
- end
9
-
10
- view :filter_items, unknown: true, wrap: :slot, template: :haml
11
-
12
- def filtered_list_input
13
- with_nest_mode :normal do
14
- class_up "card-slot", filtered_list_slot_class
15
- with_class_up "card-slot", filtered_list_slot_class do
16
- wrap do
17
- haml :filtered_list_input
18
- end
19
- end
20
- end
21
- end
22
-
23
- # NOCACHE because params alter view
24
- view :add_selected_link, cache: :never, unknown: true do
25
- link_to "Add Selected",
26
- path: { filter_card: params[:filter_card] },
27
- class: "_add-selected slotter _close-modal btn btn-primary disabled",
28
- data: { "slot-selector": ".#{params[:slot_selector]}",
29
- "item-selector": ".#{params[:item_selector]}",
30
- remote: true }
31
- end
32
-
33
- def filtered_list_item item_card
34
- nest_item item_card do |rendered, item_view|
35
- wrap_item rendered, item_view
36
- end
37
- end
38
-
39
- # for override
40
- # @return [Card] search card on which filtering is based
41
- def filter_card
42
- filter_card_from_params || default_filter_card
43
- end
44
-
45
- def default_filter_card
46
- fcard = card.options_card
47
- return fcard if fcard.respond_to? :cql_hash
48
-
49
- fcard.fetch :referred_to_by, new: {}
50
- end
51
-
52
- def filter_card_from_params
53
- return unless params[:filter_card]
54
-
55
- Card.fetch params[:filter_card], new: {}
56
- end
57
-
58
- # currently actually used as a class
59
- # (because we don't have api to override slot's id)
60
- def filtered_list_slot_class
61
- @filtered_list_slot_class ||= "filtered-list-#{unique_id}"
62
- end
63
- end
@@ -1,24 +0,0 @@
1
- %ul{class: ul_classes}
2
- - items.each.with_index do |(reference, title), index|
3
- %li.pointer-li.mb-1{"data-index": index}
4
- %span.input-group
5
- .input-group-prepend.handle
6
- %span.input-group-text
7
- = icon_tag :reorder
8
- %span.input-group-text.text-muted.group-text-left
9
- [[
10
- = text_field_tag "pointer_item", reference, class: "_reference pointer-item-text form-control group-text-middle",
11
- "data-options-card": options_card
12
- %span.input-group-text.text-muted.group-text-middle
13
- |
14
- = text_field_tag "pointer_item_title", title, class: "_title pointer-item-text form-control group-text-middle"
15
- %span.input-group-text.text-muted.group-text-right
16
- ]]
17
- .input-group-append
18
- %span.input-group-text.p-0
19
- %button.pointer-item-delete.btn.btn-secondary{ type: "button"}
20
- = icon_tag :remove
21
- %span.input-group
22
- %button._pointer-item-add.btn.btn-secondary{ type: "submit" }
23
- = icon_tag "add"
24
- add another
@@ -1,28 +0,0 @@
1
- %ul{class: ul_classes}
2
- - items.each.with_index do |(reference, title), index|
3
- %li.pointer-li.mb-1._nest-form{"data-index": index}
4
- %span.input-group
5
- .input-group-prepend.handle
6
- %span.input-group-text
7
- = icon_tag :reorder
8
- %span.input-group-text.text-muted.group-text-left
9
- {{
10
- = text_field_tag "pointer_item", reference, class: "_reference pointer-item-text form-control group-text-middle"
11
- %span.input-group-text.text-muted.group-text-middle
12
- = "|"
13
- = text_field_tag "pointer_item_title", title, class: "_title _nest-options pointer-item-text form-control group-text-middle"
14
- %span.input-group-text.text-muted.group-text-right
15
- }}
16
- .input-group-append
17
- %span.input-group-text.p-0
18
- %button.btn.btn-secondary._open-nest-editor{ type: "button"}
19
- = icon_tag :edit
20
- .input-group-append
21
- %span.input-group-text.p-0
22
- %button.pointer-item-delete.btn.btn-secondary{ type: "button"}
23
- = icon_tag :remove
24
-
25
- %span.input-group
26
- %button._pointer-item-add.btn.btn-secondary{ type: "submit" }
27
- = icon_tag "add"
28
- add another