ariadne_view_components 0.0.57-x86_64-darwin → 0.0.59-x86_64-darwin
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/app/assets/builds/ariadne_view_components.css +0 -9
- data/app/assets/javascripts/ariadne_view_components.js +2 -2
- data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
- data/app/components/ariadne/ariadne.js +0 -2
- data/app/components/ariadne/ariadne.ts +0 -2
- data/lib/ariadne/view_components/version.rb +1 -1
- data/static/audited_at.json +0 -2
- data/static/constants.json +0 -11
- data/static/statuses.json +0 -2
- metadata +2 -10
- data/app/assets/javascripts/components/ariadne/rich_text_area_component/rich-text-area-component.d.ts +0 -6
- data/app/components/ariadne/comment_component/comment_component.html.erb +0 -37
- data/app/components/ariadne/comment_component.rb +0 -71
- data/app/components/ariadne/rich_text_area_component/rich-text-area-component.d.ts +0 -6
- data/app/components/ariadne/rich_text_area_component/rich-text-area-component.js +0 -38
- data/app/components/ariadne/rich_text_area_component/rich-text-area-component.ts +0 -47
- data/app/components/ariadne/rich_text_area_component/rich_text_area_component.html.erb +0 -6
- data/app/components/ariadne/rich_text_area_component.rb +0 -35
@@ -6,7 +6,6 @@ import OptionsController from './options_controller/options_controller';
|
|
6
6
|
import AccumulatorController from './accumulator_controller/accumulator_controller';
|
7
7
|
import ToggleableController from './toggleable_controller/toggleable_controller';
|
8
8
|
import ClipboardCopyComponent from './clipboard_copy_component/clipboard-copy-component';
|
9
|
-
import RichTextAreaComponent from './rich_text_area_component/rich-text-area-component';
|
10
9
|
import SlideoverComponent from './slideover_component/slideover-component';
|
11
10
|
import TabNavComponent from './tab_nav_component/tab-nav-component';
|
12
11
|
import TooltipComponent from './tooltip_component/tooltip-component';
|
@@ -15,7 +14,6 @@ import './time_ago_component/time-ago-component';
|
|
15
14
|
const application = Application.start();
|
16
15
|
application.register('clipboard-copy-component', ClipboardCopyComponent);
|
17
16
|
application.register('ariadne-form', AriadneForm);
|
18
|
-
application.register('rich-text-area-component', RichTextAreaComponent);
|
19
17
|
application.register('slideover-component', SlideoverComponent);
|
20
18
|
application.register('tab-nav-component', TabNavComponent);
|
21
19
|
application.register('tooltip-component', TooltipComponent);
|
@@ -8,7 +8,6 @@ import OptionsController from './options_controller/options_controller'
|
|
8
8
|
import AccumulatorController from './accumulator_controller/accumulator_controller'
|
9
9
|
import ToggleableController from './toggleable_controller/toggleable_controller'
|
10
10
|
import ClipboardCopyComponent from './clipboard_copy_component/clipboard-copy-component'
|
11
|
-
import RichTextAreaComponent from './rich_text_area_component/rich-text-area-component'
|
12
11
|
import SlideoverComponent from './slideover_component/slideover-component'
|
13
12
|
import TabNavComponent from './tab_nav_component/tab-nav-component'
|
14
13
|
import TooltipComponent from './tooltip_component/tooltip-component'
|
@@ -20,7 +19,6 @@ const application = Application.start()
|
|
20
19
|
|
21
20
|
application.register('clipboard-copy-component', ClipboardCopyComponent)
|
22
21
|
application.register('ariadne-form', AriadneForm)
|
23
|
-
application.register('rich-text-area-component', RichTextAreaComponent)
|
24
22
|
application.register('slideover-component', SlideoverComponent)
|
25
23
|
application.register('tab-nav-component', TabNavComponent)
|
26
24
|
application.register('tooltip-component', TooltipComponent)
|
data/static/audited_at.json
CHANGED
@@ -14,7 +14,6 @@
|
|
14
14
|
"Ariadne::ClipboardCopyComponent": "",
|
15
15
|
"Ariadne::CloseButtonComponent": "",
|
16
16
|
"Ariadne::ComboboxComponent": "",
|
17
|
-
"Ariadne::CommentComponent": "",
|
18
17
|
"Ariadne::ContainerComponent": "",
|
19
18
|
"Ariadne::Content": "",
|
20
19
|
"Ariadne::CounterComponent": "",
|
@@ -44,7 +43,6 @@
|
|
44
43
|
"Ariadne::PopoverComponent": "",
|
45
44
|
"Ariadne::ProgressBarComponent": "",
|
46
45
|
"Ariadne::RelativeTimeComponent": "",
|
47
|
-
"Ariadne::RichTextAreaComponent": "",
|
48
46
|
"Ariadne::ShowMoreButtonComponent": "",
|
49
47
|
"Ariadne::SlideoverComponent": "",
|
50
48
|
"Ariadne::SpinnerComponent": "",
|
data/static/constants.json
CHANGED
@@ -253,13 +253,6 @@
|
|
253
253
|
"div"
|
254
254
|
]
|
255
255
|
},
|
256
|
-
"Ariadne::CommentComponent": {
|
257
|
-
"DEFAULT_CLASSES": "ariadne-border-gray-300 ariadne-border ariadne-shadow ariadne-py-5 ariadne-px-5 ariadne-rounded-md ",
|
258
|
-
"DEFAULT_TAG": "div",
|
259
|
-
"TAG_OPTIONS": [
|
260
|
-
"div"
|
261
|
-
]
|
262
|
-
},
|
263
256
|
"Ariadne::ContainerComponent": {
|
264
257
|
"DEFAULT_CLASSES": "ariadne-px-4 sm:ariadne-px-6 lg:ariadne-px-8"
|
265
258
|
},
|
@@ -698,10 +691,6 @@
|
|
698
691
|
"span"
|
699
692
|
]
|
700
693
|
},
|
701
|
-
"Ariadne::RichTextAreaComponent": {
|
702
|
-
"DEFAULT_CLASSES": "",
|
703
|
-
"DEFAULT_TAG": "div"
|
704
|
-
},
|
705
694
|
"Ariadne::ShowMoreButtonComponent": {
|
706
695
|
"DEFAULT_ATTRIBUTES": {
|
707
696
|
"wrapper": {
|
data/static/statuses.json
CHANGED
@@ -14,7 +14,6 @@
|
|
14
14
|
"Ariadne::ClipboardCopyComponent": "stable",
|
15
15
|
"Ariadne::CloseButtonComponent": "stable",
|
16
16
|
"Ariadne::ComboboxComponent": "stable",
|
17
|
-
"Ariadne::CommentComponent": "stable",
|
18
17
|
"Ariadne::ContainerComponent": "stable",
|
19
18
|
"Ariadne::Content": "stable",
|
20
19
|
"Ariadne::CounterComponent": "stable",
|
@@ -44,7 +43,6 @@
|
|
44
43
|
"Ariadne::PopoverComponent": "stable",
|
45
44
|
"Ariadne::ProgressBarComponent": "stable",
|
46
45
|
"Ariadne::RelativeTimeComponent": "stable",
|
47
|
-
"Ariadne::RichTextAreaComponent": "stable",
|
48
46
|
"Ariadne::ShowMoreButtonComponent": "stable",
|
49
47
|
"Ariadne::SlideoverComponent": "stable",
|
50
48
|
"Ariadne::SpinnerComponent": "stable",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ariadne_view_components
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.59
|
5
5
|
platform: x86_64-darwin
|
6
6
|
authors:
|
7
7
|
- Garen J. Torikian
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tailwind_merge
|
@@ -122,7 +122,6 @@ files:
|
|
122
122
|
- app/assets/javascripts/components/ariadne/events_controller/events_controller.d.ts
|
123
123
|
- app/assets/javascripts/components/ariadne/options_controller/options_controller.d.ts
|
124
124
|
- app/assets/javascripts/components/ariadne/outlet_manager_controller/outlet_manager_controller.d.ts
|
125
|
-
- app/assets/javascripts/components/ariadne/rich_text_area_component/rich-text-area-component.d.ts
|
126
125
|
- app/assets/javascripts/components/ariadne/slideover_component/slideover-component.d.ts
|
127
126
|
- app/assets/javascripts/components/ariadne/string_match_controller/string_match_controller.d.ts
|
128
127
|
- app/assets/javascripts/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.d.ts
|
@@ -173,8 +172,6 @@ files:
|
|
173
172
|
- app/components/ariadne/close_button_component.rb
|
174
173
|
- app/components/ariadne/combobox_component.html.erb
|
175
174
|
- app/components/ariadne/combobox_component.rb
|
176
|
-
- app/components/ariadne/comment_component.rb
|
177
|
-
- app/components/ariadne/comment_component/comment_component.html.erb
|
178
175
|
- app/components/ariadne/component.rb
|
179
176
|
- app/components/ariadne/container_component.rb
|
180
177
|
- app/components/ariadne/container_component/container_component.html.erb
|
@@ -233,11 +230,6 @@ files:
|
|
233
230
|
- app/components/ariadne/progress_bar_component.rb
|
234
231
|
- app/components/ariadne/relative_time_component.html.erb
|
235
232
|
- app/components/ariadne/relative_time_component.rb
|
236
|
-
- app/components/ariadne/rich_text_area_component.rb
|
237
|
-
- app/components/ariadne/rich_text_area_component/rich-text-area-component.d.ts
|
238
|
-
- app/components/ariadne/rich_text_area_component/rich-text-area-component.js
|
239
|
-
- app/components/ariadne/rich_text_area_component/rich-text-area-component.ts
|
240
|
-
- app/components/ariadne/rich_text_area_component/rich_text_area_component.html.erb
|
241
233
|
- app/components/ariadne/show_more_button_component.html.erb
|
242
234
|
- app/components/ariadne/show_more_button_component.rb
|
243
235
|
- app/components/ariadne/slideover_component.rb
|
@@ -1,37 +0,0 @@
|
|
1
|
-
<%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do %>
|
2
|
-
<%= render(Ariadne::TabContainerComponent.new(sr_label: @sr_label)) do |tab_container| %>
|
3
|
-
<%= tab_container.with_tab(id: public_tab.id, selected: public_tab.selected, classes: public_tab.classes, attributes: public_tab.attributes) do |tab| %>
|
4
|
-
<% tab.with_text { @public_tab_text } %>
|
5
|
-
<% tab.with_panel(attributes: {:"data-public" => true}) do %>
|
6
|
-
<%= ariadne_form_with(url: @url, method: @method, classes: @classes, attributes: @attributes) do |comment_box| %>
|
7
|
-
<% @hidden_fields.each do |name, value| %>
|
8
|
-
<%= hidden_field_tag name, value %>
|
9
|
-
<% end %>
|
10
|
-
<div class="ariadne-overflow-hidden ariadne-border ariadne-border-slate-300 ariadne-shadow-sm">
|
11
|
-
<%= hidden_field_tag 'message_is_public', true %>
|
12
|
-
<%= render(Ariadne::RichTextAreaComponent.new(name: :message_public_bodytext, sr_label: "Select reply type", attributes: { required: true})) %>
|
13
|
-
<% comment_box.submit { @submit } %>
|
14
|
-
</div>
|
15
|
-
<div class="ariadne-py-2 ariadne-flex ariadne-justify-end">
|
16
|
-
<%= public_submit %>
|
17
|
-
</div>
|
18
|
-
<% end %>
|
19
|
-
<% end %>
|
20
|
-
<% end %>
|
21
|
-
<%= tab_container.with_tab(id: internal_tab.id, selected: internal_tab.selected, classes: internal_tab.classes, attributes: internal_tab.attributes) do |tab| %>
|
22
|
-
<% tab.with_text { @internal_tab_text } %>
|
23
|
-
<% tab.with_panel do %>
|
24
|
-
<%= ariadne_form_with(url: @url, method: @method, classes: @classes, attributes: @attributes) do |comment_box| %>
|
25
|
-
<div class="ariadne-overflow-hidden ariadne-border ariadne-border-gray-300 ariadne-shadow-sm">
|
26
|
-
<%= hidden_field_tag 'message_is_public', false %>
|
27
|
-
<%= render(Ariadne::RichTextAreaComponent.new(name: :message_internal_bodytext, sr_label: "Select reply type", attributes: { required: true})) %>
|
28
|
-
<% comment_box.submit { @submit } %>
|
29
|
-
</div>
|
30
|
-
<div class="ariadne-py-2 ariadne-flex ariadne-justify-end">
|
31
|
-
<%= internal_submit %>
|
32
|
-
</div>
|
33
|
-
<% end %>
|
34
|
-
<% end %>
|
35
|
-
<% end %>
|
36
|
-
<% end %>
|
37
|
-
<% end %>
|
@@ -1,71 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Ariadne
|
4
|
-
# Defines a submittable form for adding comments to a conversation.
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# @accessibility This component requires you to pass in a `sr_label`
|
8
|
-
# attribute, which will be used to label the tabs for screen readers.
|
9
|
-
class CommentComponent < Ariadne::Component
|
10
|
-
DEFAULT_TAG = :div
|
11
|
-
TAG_OPTIONS = [DEFAULT_TAG].freeze
|
12
|
-
|
13
|
-
DEFAULT_CLASSES = "ariadne-border-gray-300 ariadne-border ariadne-shadow ariadne-py-5 ariadne-px-5 ariadne-rounded-md "
|
14
|
-
|
15
|
-
renders_one :public_tab, lambda { |selected: false, text:, classes: "", attributes: {}|
|
16
|
-
attributes[:"data-public"] = true
|
17
|
-
@tab_idx += 1
|
18
|
-
id = attributes.fetch(:id, "public-tab-#{@tab_idx}")
|
19
|
-
@public_tab_text = text
|
20
|
-
|
21
|
-
Ariadne::TabComponent.new(selected: selected, classes: classes, id: id, with_panel: true, attributes: attributes)
|
22
|
-
}
|
23
|
-
|
24
|
-
renders_one :public_submit, lambda { |classes: "", attributes: {}|
|
25
|
-
Ariadne::ButtonComponent.new(type: Ariadne::BaseButton::TYPE_SUBMIT, scheme: Ariadne::ButtonComponent::DEFAULT_SCHEME, classes: classes, attributes: attributes)
|
26
|
-
}
|
27
|
-
|
28
|
-
renders_one :internal_tab, lambda { |selected: false, text:, classes: "", attributes: {}|
|
29
|
-
attributes[:"data-public"] = false
|
30
|
-
@tab_idx += 1
|
31
|
-
id = attributes.fetch(:id, "internal-tab-#{@tab_idx}")
|
32
|
-
@internal_tab_text = text
|
33
|
-
|
34
|
-
Ariadne::TabComponent.new(selected: selected, classes: classes, id: id, with_panel: true, attributes: attributes)
|
35
|
-
}
|
36
|
-
|
37
|
-
renders_one :internal_submit, lambda { |classes: "", attributes: {}|
|
38
|
-
Ariadne::ButtonComponent.new(type: Ariadne::BaseButton::TYPE_SUBMIT, scheme: Ariadne::ButtonComponent::DEFAULT_SCHEME, classes: classes, attributes: attributes)
|
39
|
-
}
|
40
|
-
|
41
|
-
# @example Default
|
42
|
-
#
|
43
|
-
# <%= render(Ariadne::CommentComponent.new(url: "/messages", method: :post, sr_label: "Select delivery time")) do |comment| %>
|
44
|
-
# <% comment.with_public_tab(selected: true, text: "Reply to sender") %>
|
45
|
-
# <% comment.with_public_submit { "Send" } %>
|
46
|
-
# <% comment.with_internal_tab(text: "Internal comment") %>
|
47
|
-
# <% comment.with_internal_submit { "Send" } %>
|
48
|
-
# <% end %>
|
49
|
-
#
|
50
|
-
# @param url [String] The URL to take action against.
|
51
|
-
# @param method [String] The method to use when submitting the form.
|
52
|
-
# @param sr_label [String] A label to introduce these tabs for screen readers.
|
53
|
-
# @param hidden_fields [[String]] An array of arrays of additional (hidden) fields to add to the form.
|
54
|
-
# @param classes [String] <%= link_to_classes_docs %>
|
55
|
-
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
56
|
-
def initialize(url:, method: :post, sr_label:, hidden_fields: [], classes: "", attributes: {})
|
57
|
-
@tag = DEFAULT_TAG
|
58
|
-
@classes = merge_class_names(
|
59
|
-
DEFAULT_CLASSES,
|
60
|
-
classes,
|
61
|
-
)
|
62
|
-
@url = url
|
63
|
-
@method = method
|
64
|
-
@sr_label = sr_label
|
65
|
-
|
66
|
-
@tab_idx = -1
|
67
|
-
@hidden_fields = hidden_fields
|
68
|
-
@attributes = attributes
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
import { Controller } from '@hotwired/stimulus';
|
2
|
-
import { Editor } from '@tiptap/core';
|
3
|
-
import { Document } from '@tiptap/extension-document';
|
4
|
-
import { Paragraph } from '@tiptap/extension-paragraph';
|
5
|
-
import { Text } from '@tiptap/extension-text';
|
6
|
-
import DropCursor from '@tiptap/extension-dropcursor';
|
7
|
-
import GapCursor from '@tiptap/extension-gapcursor';
|
8
|
-
import { History } from '@tiptap/extension-history';
|
9
|
-
class RichTextArea extends Controller {
|
10
|
-
connect() {
|
11
|
-
for (const editorElement of this.editorTargets) {
|
12
|
-
const pmEditor = new Editor({
|
13
|
-
extensions: [DropCursor, GapCursor, History, Document, Paragraph, Text],
|
14
|
-
content: '',
|
15
|
-
injectCSS: false,
|
16
|
-
element: editorElement,
|
17
|
-
editorProps: {
|
18
|
-
attributes: {
|
19
|
-
class: 'ariadne-h-28 ariadne-max-h-48 ariadne-p-2 ariadne-rounded-lg ariadne-overflow-y-auto focus:ariadne-outline-none',
|
20
|
-
},
|
21
|
-
},
|
22
|
-
parseOptions: {
|
23
|
-
preserveWhitespace: true,
|
24
|
-
},
|
25
|
-
});
|
26
|
-
const tiptapValueContainer = editorElement.previousElementSibling;
|
27
|
-
if (tiptapValueContainer) {
|
28
|
-
const parentForm = editorElement.closest('form');
|
29
|
-
parentForm === null || parentForm === void 0 ? void 0 : parentForm.addEventListener('submit', () => {
|
30
|
-
tiptapValueContainer.setAttribute('value', pmEditor.getText() || '');
|
31
|
-
pmEditor.commands.clearContent(); // TODO: test this
|
32
|
-
});
|
33
|
-
}
|
34
|
-
}
|
35
|
-
}
|
36
|
-
}
|
37
|
-
RichTextArea.targets = ['editor'];
|
38
|
-
export default RichTextArea;
|
@@ -1,47 +0,0 @@
|
|
1
|
-
import {Controller} from '@hotwired/stimulus'
|
2
|
-
|
3
|
-
import {Editor} from '@tiptap/core'
|
4
|
-
|
5
|
-
import {Document} from '@tiptap/extension-document'
|
6
|
-
import {Paragraph} from '@tiptap/extension-paragraph'
|
7
|
-
import {Text} from '@tiptap/extension-text'
|
8
|
-
|
9
|
-
import DropCursor from '@tiptap/extension-dropcursor'
|
10
|
-
import GapCursor from '@tiptap/extension-gapcursor'
|
11
|
-
import {History} from '@tiptap/extension-history'
|
12
|
-
|
13
|
-
export default class RichTextArea extends Controller {
|
14
|
-
static targets = ['editor']
|
15
|
-
|
16
|
-
declare readonly editorTargets: [HTMLDivElement]
|
17
|
-
|
18
|
-
connect() {
|
19
|
-
for (const editorElement of this.editorTargets) {
|
20
|
-
const pmEditor = new Editor({
|
21
|
-
extensions: [DropCursor, GapCursor, History, Document, Paragraph, Text],
|
22
|
-
content: '',
|
23
|
-
injectCSS: false,
|
24
|
-
element: editorElement,
|
25
|
-
editorProps: {
|
26
|
-
attributes: {
|
27
|
-
class:
|
28
|
-
'ariadne-h-28 ariadne-max-h-48 ariadne-p-2 ariadne-rounded-lg ariadne-overflow-y-auto focus:ariadne-outline-none',
|
29
|
-
},
|
30
|
-
},
|
31
|
-
parseOptions: {
|
32
|
-
preserveWhitespace: true,
|
33
|
-
},
|
34
|
-
})
|
35
|
-
|
36
|
-
const tiptapValueContainer = editorElement.previousElementSibling
|
37
|
-
if (tiptapValueContainer) {
|
38
|
-
const parentForm = editorElement.closest('form')
|
39
|
-
|
40
|
-
parentForm?.addEventListener('submit', () => {
|
41
|
-
tiptapValueContainer.setAttribute('value', pmEditor.getText() || '')
|
42
|
-
pmEditor.commands.clearContent() // TODO: test this
|
43
|
-
})
|
44
|
-
}
|
45
|
-
}
|
46
|
-
}
|
47
|
-
}
|
@@ -1,6 +0,0 @@
|
|
1
|
-
<%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do %>
|
2
|
-
<% if @has_form %>
|
3
|
-
<input type="hidden" data-tiptap-value-container=true name="<%= @name %>" id="<%= @name %>">
|
4
|
-
<% end %>
|
5
|
-
<div class="tiptap-editor" name="rich-text-area" data-rich-text-area-component-target="editor"></div>
|
6
|
-
<% end %>
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Ariadne
|
4
|
-
# Add a general description of component here
|
5
|
-
# Add additional usage considerations or best practices that may aid the user to use the component correctly.
|
6
|
-
# @accessibility Add any accessibility considerations
|
7
|
-
class RichTextAreaComponent < Ariadne::Component
|
8
|
-
DEFAULT_TAG = :div
|
9
|
-
DEFAULT_CLASSES = ""
|
10
|
-
|
11
|
-
# @example Default
|
12
|
-
#
|
13
|
-
# <%= render(Ariadne::RichTextAreaComponent.new(name: "bodytext", sr_label: "Enter message contents")) { "Example" } %>
|
14
|
-
#
|
15
|
-
# @param name [Symbol] Identifies the form/param name for this rich text area.
|
16
|
-
# @param sr_label [String] A label to introduce these tabs for screen readers.
|
17
|
-
# @param has_form [Boolean] Indicates whether this component is wrapped in a form.
|
18
|
-
# @param classes [String] <%= link_to_classes_docs %>
|
19
|
-
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
20
|
-
def initialize(name:, sr_label:, has_form: true, classes: "", attributes: {})
|
21
|
-
@tag = DEFAULT_TAG
|
22
|
-
@name = name
|
23
|
-
@sr_label = sr_label
|
24
|
-
@has_form = has_form
|
25
|
-
|
26
|
-
@classes = merge_class_names(
|
27
|
-
DEFAULT_CLASSES,
|
28
|
-
classes,
|
29
|
-
)
|
30
|
-
@attributes = attributes
|
31
|
-
@attributes[:"data-controller"] = "rich-text-area-component"
|
32
|
-
@attributes[:"data-has-form"] = true if @has_form
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|