tramway 2.3 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -0
- data/app/components/tramway/chat_component.html.haml +75 -26
- data/app/components/tramway/native_text_component.html.haml +2 -3
- data/app/components/tramway/native_text_component.rb +133 -12
- data/app/views/tramway/chats/_message.html.haml +4 -0
- data/config/tailwind.config.js +5 -0
- data/docs/AGENTS.md +16 -0
- data/lib/tramway/chats/broadcast.rb +19 -0
- data/lib/tramway/engine.rb +11 -0
- data/lib/tramway/version.rb +1 -1
- data/lib/tramway.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0062ba6e33495a79ef0d2dc0fcca348e7de9ed5a892855d800bc7de45022205d
|
|
4
|
+
data.tar.gz: 350731c7514a5b6f3d513635c2397f65a52f81cb50a05ff0e198af191a09b75d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c5ad624bc5aa06c742ac6a02cbaea4accfe21028ec422f5818514a3bb87b854f06e830986f35d767a2fe82f06f0d3ab433e615651522e8ab332d46925e54c58
|
|
7
|
+
data.tar.gz: ffd97ea24ee55e590368a3a34beafcb440010358a65db27d0e231629225bcd05f0d0f22987c38f6dc7ebf97193ebc6f26ba76628280b2e1e92169f1b20dd69fa
|
data/README.md
CHANGED
|
@@ -884,9 +884,28 @@ message fields like `text`, `data`, or `sent_at` are forwarded to `tramway/chats
|
|
|
884
884
|
send_message_path: chat_messages_path %>
|
|
885
885
|
```
|
|
886
886
|
|
|
887
|
+
|
|
887
888
|
If you do not want to render the message form, pass `message_form: nil`. When the form is present, `send_message_path` is
|
|
888
889
|
required and the helper will generate the correct POST form.
|
|
889
890
|
|
|
891
|
+
To append messages to an already-rendered `tramway_chat` stream, use `tramway_chat_append_message`.
|
|
892
|
+
The method is mixed into controllers and ActiveRecord models by Tramway and expects:
|
|
893
|
+
- `chat_id:` — the same value used in `tramway_chat chat_id:`
|
|
894
|
+
- `message_type:` — only `:sent` or `:received` (raises `ArgumentError` otherwise)
|
|
895
|
+
- `text:` — message content
|
|
896
|
+
- `sent_at:` — message timestamp
|
|
897
|
+
|
|
898
|
+
```ruby
|
|
899
|
+
tramway_chat_append_message(
|
|
900
|
+
chat_id: 'support-chat',
|
|
901
|
+
message_type: :received,
|
|
902
|
+
text: 'We got your request',
|
|
903
|
+
sent_at: Time.current
|
|
904
|
+
)
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
It broadcasts with `target: 'messages'` and renders the `tramway/chats/message` partial so the message appears in the live chat.
|
|
908
|
+
|
|
890
909
|
### Tramway Table Component
|
|
891
910
|
|
|
892
911
|
Tramway provides a responsive, tailwind-styled table with light and dark themes. Use the `tramway_table`, `tramway_row`, and
|
|
@@ -1,28 +1,77 @@
|
|
|
1
1
|
= helpers.turbo_stream_from chat_id, 'messages'
|
|
2
2
|
|
|
3
|
-
.flex-1.min-h-0.
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
#chat.flex.flex-1.h-full.w-full.min-h-0.min-w-0.flex-col
|
|
4
|
+
#messages.flex.flex-col.flex-1.min-h-0.overflow-y-auto.p-2.md:p-6.space-y-2.md:space-y-4.md:rounded-xl.rounded-t-xl{ class: 'text-gray-100 bg-gray-800/60' }
|
|
5
|
+
- messages.each do |message|
|
|
6
|
+
= component "tramway/chats/message", **message
|
|
7
|
+
- if disabled?
|
|
8
|
+
= inline_svg 'icons/dots.svg', class: 'w-8 h-8 text-gray-500 mx-auto my-4'
|
|
9
|
+
|
|
10
|
+
- if message_form.present?
|
|
11
|
+
.shrink-0.border-gray-200.md:pt-2.border-gray-700
|
|
12
|
+
= form_for message_form, url: send_message_path, as: :message, method: :post, html: { class: 'flex items-center md:gap-1' } do |f|
|
|
13
|
+
- waiting_placeholder = t('tramway.chat.placeholders.waiting')
|
|
14
|
+
- typing_placeholder = t('tramway.chat.placeholders.type')
|
|
15
|
+
= f.text_field :text,
|
|
16
|
+
class: 'flex-1 md:rounded-full rounded-bl-2xl md:border border-gray-300 px-4 py-2 text-sm text-gray-900 shadow-sm placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 disabled:cursor-not-allowed disabled:bg-gray-100 border-gray-600 bg-gray-800 text-white focus:border-blue-400 focus:ring-blue-500/30',
|
|
17
|
+
placeholder: send_messages_enabled ? typing_placeholder : waiting_placeholder,
|
|
18
|
+
disabled: !send_messages_enabled
|
|
19
|
+
|
|
20
|
+
= f.hidden_field :chat_id, value: chat_id
|
|
21
|
+
|
|
22
|
+
- options.each do |(key, value)|
|
|
23
|
+
= f.hidden_field key, value: value
|
|
24
|
+
|
|
25
|
+
= f.submit '🡩',
|
|
26
|
+
class: 'md:rounded-full bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 bg-blue-500 hover:bg-blue-400 cursor-pointer'
|
|
27
|
+
|
|
28
|
+
:javascript
|
|
29
|
+
(() => {
|
|
30
|
+
const setupChatScroll = () => {
|
|
31
|
+
const messages = document.getElementById('messages');
|
|
32
|
+
if (!messages || messages.dataset.chatScrollInitialized === 'true') return;
|
|
33
|
+
|
|
34
|
+
const scrollToBottom = () => {
|
|
35
|
+
messages.scrollTop = messages.scrollHeight;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
scrollToBottom();
|
|
39
|
+
|
|
40
|
+
const observer = new MutationObserver(scrollToBottom);
|
|
41
|
+
observer.observe(messages, { childList: true });
|
|
42
|
+
|
|
43
|
+
messages.dataset.chatScrollInitialized = 'true';
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const setupChatInputClear = () => {
|
|
47
|
+
const forms = document.querySelectorAll('form[action][method]');
|
|
48
|
+
|
|
49
|
+
forms.forEach((form) => {
|
|
50
|
+
if (form.dataset.chatInputClearInitialized === 'true') return;
|
|
51
|
+
|
|
52
|
+
const textInput = form.querySelector('input[name="message[text]"]');
|
|
53
|
+
if (!textInput) return;
|
|
54
|
+
|
|
55
|
+
form.addEventListener('turbo:submit-end', (event) => {
|
|
56
|
+
if (event.detail.success) {
|
|
57
|
+
textInput.value = '';
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
form.dataset.chatInputClearInitialized = 'true';
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (document.readyState === 'loading') {
|
|
66
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
67
|
+
setupChatScroll();
|
|
68
|
+
setupChatInputClear();
|
|
69
|
+
}, { once: true });
|
|
70
|
+
} else {
|
|
71
|
+
setupChatScroll();
|
|
72
|
+
setupChatInputClear();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
document.addEventListener('turbo:load', setupChatScroll);
|
|
76
|
+
document.addEventListener('turbo:load', setupChatInputClear);
|
|
77
|
+
})();
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
= line
|
|
1
|
+
%div
|
|
2
|
+
= rendered_html
|
|
@@ -1,27 +1,148 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'erb'
|
|
4
|
+
|
|
3
5
|
module Tramway
|
|
4
6
|
# Displays text with size-based utility classes.
|
|
5
7
|
class NativeTextComponent < Tramway::BaseComponent
|
|
8
|
+
URL_REGEX = %r{https?://[^\s<]+}
|
|
9
|
+
MAX_URL_LENGTH = 41
|
|
10
|
+
HEADER_REGEX = /\A(#{Regexp.escape('#')}{1,6})\s+(.+)\z/
|
|
11
|
+
LIST_ITEM_REGEX = /\A[-*]\s+(.+)\z/
|
|
12
|
+
HEADER_CLASSES = {
|
|
13
|
+
1 => 'text-4xl font-bold leading-tight mt-4 mb-2',
|
|
14
|
+
2 => 'text-3xl font-bold leading-tight mt-4 mb-2',
|
|
15
|
+
3 => 'text-2xl font-semibold leading-snug mt-3 mb-2',
|
|
16
|
+
4 => 'text-xl font-semibold leading-snug mt-3 mb-2',
|
|
17
|
+
5 => 'text-lg font-semibold leading-snug mt-2 mb-1',
|
|
18
|
+
6 => 'text-base font-semibold leading-snug mt-2 mb-1'
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
6
21
|
option :text
|
|
7
22
|
option :size, optional: true, default: -> { :middle }
|
|
8
23
|
option :klass, optional: true, default: -> { '' }
|
|
9
24
|
|
|
10
|
-
def
|
|
11
|
-
|
|
25
|
+
def rendered_html
|
|
26
|
+
# Safe because this is an empty static string literal, not user-controlled content.
|
|
27
|
+
return ''.html_safe if text.blank?
|
|
28
|
+
|
|
29
|
+
helpers.safe_join(rendered_blocks)
|
|
12
30
|
end
|
|
13
31
|
|
|
14
32
|
def text_class
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
{ small: 'text-sm', large: 'text-lg', middle: 'text-base' }.fetch(size, 'md:text-sm lg:text-base') + " #{klass}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# rubocop:disable Metrics/AbcSize
|
|
39
|
+
# rubocop:disable Metrics/MethodLength
|
|
40
|
+
def rendered_blocks
|
|
41
|
+
blocks = []
|
|
42
|
+
list_items = []
|
|
43
|
+
|
|
44
|
+
text.split("\n").each do |line|
|
|
45
|
+
stripped_line = line.strip
|
|
46
|
+
|
|
47
|
+
if stripped_line.blank?
|
|
48
|
+
flush_list_items(blocks, list_items)
|
|
49
|
+
next
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
header_match = stripped_line.match(HEADER_REGEX)
|
|
53
|
+
if header_match
|
|
54
|
+
flush_list_items(blocks, list_items)
|
|
55
|
+
blocks << helpers.content_tag(
|
|
56
|
+
"h#{header_match[1].length}",
|
|
57
|
+
render_inline_markdown(header_match[2]),
|
|
58
|
+
class: header_class(header_match[1].length)
|
|
59
|
+
)
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
list_match = stripped_line.match(LIST_ITEM_REGEX)
|
|
64
|
+
if list_match
|
|
65
|
+
list_items << helpers.content_tag(:li, render_inline_markdown(list_match[1]),
|
|
66
|
+
class: "#{text_class} marker:hidden")
|
|
67
|
+
next
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
flush_list_items(blocks, list_items)
|
|
71
|
+
blocks << helpers.content_tag(:p, render_inline_markdown(stripped_line), class: "#{text_class} my-1")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
flush_list_items(blocks, list_items)
|
|
75
|
+
blocks
|
|
76
|
+
end
|
|
77
|
+
# rubocop:enable Metrics/AbcSize
|
|
78
|
+
# rubocop:enable Metrics/MethodLength
|
|
79
|
+
|
|
80
|
+
def flush_list_items(blocks, list_items)
|
|
81
|
+
return if list_items.empty?
|
|
82
|
+
|
|
83
|
+
blocks << helpers.content_tag(:ul, helpers.safe_join(list_items), class: "list-none pl-5 my-2 #{klass}")
|
|
84
|
+
list_items.clear
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def header_class(level)
|
|
88
|
+
"#{HEADER_CLASSES.fetch(level)} #{klass}".strip
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render_inline_markdown(content)
|
|
92
|
+
escaped_content = ERB::Util.html_escape(content)
|
|
93
|
+
with_bold = escaped_content.gsub(/\*\*(.+?)\*\*/, '<strong>\\1</strong>')
|
|
94
|
+
with_italics = with_bold.gsub(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/, '<em>\\1</em>')
|
|
95
|
+
with_underscored_italics = with_italics.gsub(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/, '<em>\\1</em>')
|
|
96
|
+
# Safe because user input has already been escaped above and only controlled tags are introduced.
|
|
97
|
+
# rubocop:disable Rails/OutputSafety
|
|
98
|
+
linkified(with_underscored_italics).html_safe
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# rubocop:disable Metrics/MethodLength
|
|
102
|
+
# rubocop:disable Metrics/AbcSize
|
|
103
|
+
def linkified(content)
|
|
104
|
+
fragments = []
|
|
105
|
+
current_index = 0
|
|
106
|
+
|
|
107
|
+
content.to_enum(:scan, URL_REGEX).map do
|
|
108
|
+
match = Regexp.last_match
|
|
109
|
+
matched_url = match[0]
|
|
110
|
+
url, trailing = strip_trailing_punctuation(matched_url)
|
|
111
|
+
|
|
112
|
+
# Safe because this fragment is sliced from `content`, which was already HTML-escaped.
|
|
113
|
+
|
|
114
|
+
fragments << content[current_index...match.begin(0)].html_safe # rubocop:disable Rails/OutputSafety
|
|
115
|
+
fragments << helpers.link_to(
|
|
116
|
+
shorten(url),
|
|
117
|
+
url,
|
|
118
|
+
target: '_blank',
|
|
119
|
+
rel: 'noopener noreferrer',
|
|
120
|
+
class: 'text-blue-400 hover:underline'
|
|
121
|
+
)
|
|
122
|
+
# Safe because `trailing` can only contain stripped URL punctuation (.,!?;:).
|
|
123
|
+
fragments << trailing.html_safe if trailing.present? # rubocop:disable Rails/OutputSafety
|
|
124
|
+
current_index = match.end(0)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Safe because this tail fragment also comes from the already escaped `content` string.
|
|
128
|
+
fragments << content[current_index..].html_safe if current_index < content.length
|
|
129
|
+
# rubocop:enable Rails/OutputSafety
|
|
130
|
+
|
|
131
|
+
helpers.safe_join(fragments)
|
|
132
|
+
end
|
|
133
|
+
# rubocop:enable Metrics/AbcSize
|
|
134
|
+
# rubocop:enable Metrics/MethodLength
|
|
135
|
+
|
|
136
|
+
def strip_trailing_punctuation(url)
|
|
137
|
+
stripped_url = url.sub(/[.,!?;:]+\z/, '')
|
|
138
|
+
trailing = url.delete_prefix(stripped_url)
|
|
139
|
+
[stripped_url, trailing]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def shorten(url)
|
|
143
|
+
return url if url.length <= MAX_URL_LENGTH
|
|
144
|
+
|
|
145
|
+
"#{url[0...MAX_URL_LENGTH]}..."
|
|
25
146
|
end
|
|
26
147
|
end
|
|
27
148
|
end
|
data/config/tailwind.config.js
CHANGED
|
@@ -48,6 +48,7 @@ module.exports = {
|
|
|
48
48
|
'hover:bg-blue-400',
|
|
49
49
|
'text-gray-100',
|
|
50
50
|
'text-gray-400',
|
|
51
|
+
'text-blue-400',
|
|
51
52
|
'placeholder:text-gray-400',
|
|
52
53
|
'focus:border-blue-400',
|
|
53
54
|
'focus:ring-blue-500/30',
|
|
@@ -85,6 +86,7 @@ module.exports = {
|
|
|
85
86
|
'bg-blue-600',
|
|
86
87
|
'bg-gray-800/60',
|
|
87
88
|
'min-h-0',
|
|
89
|
+
'min-w-0',
|
|
88
90
|
'flex-1',
|
|
89
91
|
'overflow-y-auto',
|
|
90
92
|
'overflow-hidden',
|
|
@@ -102,6 +104,9 @@ module.exports = {
|
|
|
102
104
|
'rounded-bl-2xl',
|
|
103
105
|
'md:border',
|
|
104
106
|
'md:gap-1',
|
|
107
|
+
'hover:underline',
|
|
108
|
+
'marker:hidden',
|
|
109
|
+
'list-none',
|
|
105
110
|
|
|
106
111
|
// === Custom table layout utilities ===
|
|
107
112
|
'div-table',
|
data/docs/AGENTS.md
CHANGED
|
@@ -132,6 +132,10 @@ When you need chat UI, use the `tramway_chat` helper. Pass `chat_id`, `messages`
|
|
|
132
132
|
Each message must include `:id` and a `:type` of `:sent` or `:received`, and other keys (like `:text`, `:data`, `:sent_at`)
|
|
133
133
|
are forwarded to `tramway/chats/message_component`. Use `message_form: nil` when you only need read-only chat rendering.
|
|
134
134
|
|
|
135
|
+
For live updates to a rendered `tramway_chat`, use `tramway_chat_append_message(chat_id:, message_type:, text:, sent_at:)`.
|
|
136
|
+
This method is included in all controllers and ActiveRecord models. `message_type` must be `:sent` or `:received`, otherwise
|
|
137
|
+
it raises `ArgumentError`. `chat_id` must match the stream id used in `tramway_chat`.
|
|
138
|
+
|
|
135
139
|
### Rule 9
|
|
136
140
|
If page `create` or `update` is configured for an entity, use Tramway Form pattern for forms. Visible fields are configured via `form_fields` method.
|
|
137
141
|
|
|
@@ -448,6 +452,18 @@ scope :for_role, -> (role) { where role: role }
|
|
|
448
452
|
### Rule 28
|
|
449
453
|
In case, you need to make a one link in tramway_table on each row. Use `tramway_row href: your_link` instead of putting the like inside a cell.
|
|
450
454
|
|
|
455
|
+
### Rule 29
|
|
456
|
+
Always use tramway decorated objects in views.
|
|
457
|
+
|
|
458
|
+
### Rule 30
|
|
459
|
+
For Tailwind classes with `/`, `[`, `]` characters use `{ class: 'here is the complicated class' }` in HAML.
|
|
460
|
+
|
|
461
|
+
### Rule 31
|
|
462
|
+
Always `tramway_decorate` and `tramway_form` for creating these types of objects. Don't use decorator and form classes for this.
|
|
463
|
+
|
|
464
|
+
### Rule 32
|
|
465
|
+
In Tramway Decorators, use `delegate_attributes` method instead of `delegate :something, to: :object`
|
|
466
|
+
|
|
451
467
|
## Controller Patterns
|
|
452
468
|
|
|
453
469
|
- Keep actions short and explicit with guard clauses.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
module Chats
|
|
5
|
+
# This module provides a method to broadcast new chat messages to a Tramway Chat
|
|
6
|
+
module Broadcast
|
|
7
|
+
MESSAGE_TYPES = %i[sent received].freeze
|
|
8
|
+
|
|
9
|
+
def tramway_chat_append_message(chat_id:, type:, text:, sent_at:)
|
|
10
|
+
raise ArgumentError, 'message_type must be :sent or :received' unless MESSAGE_TYPES.include?(type.to_sym)
|
|
11
|
+
|
|
12
|
+
Turbo::StreamsChannel.broadcast_append_to [chat_id, 'messages'],
|
|
13
|
+
target: 'messages',
|
|
14
|
+
partial: 'tramway/chats/message',
|
|
15
|
+
locals: { type:, text:, sent_at: }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/tramway/engine.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Tramway
|
|
|
12
12
|
load_decorator_helper
|
|
13
13
|
load_form_helper
|
|
14
14
|
load_routes_helper
|
|
15
|
+
load_chats_broadcast
|
|
15
16
|
configure_pagination if Tramway.config.pagination[:enabled]
|
|
16
17
|
end
|
|
17
18
|
|
|
@@ -67,6 +68,16 @@ module Tramway
|
|
|
67
68
|
end
|
|
68
69
|
end
|
|
69
70
|
|
|
71
|
+
def load_chats_broadcast
|
|
72
|
+
ActiveSupport.on_load(:action_controller) do |loaded_class|
|
|
73
|
+
loaded_class.include Tramway::Chats::Broadcast
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
ActiveSupport.on_load(:active_record) do |loaded_class|
|
|
77
|
+
loaded_class.include Tramway::Chats::Broadcast
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
70
81
|
def configure_pagination
|
|
71
82
|
ActiveSupport.on_load(:action_controller) do
|
|
72
83
|
# Detecting tramway views path
|
data/lib/tramway/version.rb
CHANGED
data/lib/tramway.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tramway
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kalashnikovisme
|
|
@@ -243,6 +243,7 @@ files:
|
|
|
243
243
|
- app/views/kaminari/_page.html.haml
|
|
244
244
|
- app/views/kaminari/_paginator.html.haml
|
|
245
245
|
- app/views/kaminari/_prev_page.html.haml
|
|
246
|
+
- app/views/tramway/chats/_message.html.haml
|
|
246
247
|
- app/views/tramway/entities/_form.html.haml
|
|
247
248
|
- app/views/tramway/entities/_list.html.haml
|
|
248
249
|
- app/views/tramway/entities/edit.html.haml
|
|
@@ -261,6 +262,7 @@ files:
|
|
|
261
262
|
- lib/tramway.rb
|
|
262
263
|
- lib/tramway/base_decorator.rb
|
|
263
264
|
- lib/tramway/base_form.rb
|
|
265
|
+
- lib/tramway/chats/broadcast.rb
|
|
264
266
|
- lib/tramway/config.rb
|
|
265
267
|
- lib/tramway/configs/entities/page.rb
|
|
266
268
|
- lib/tramway/configs/entities/route.rb
|