rails6-footnotes 5.0.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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +25 -0
- data/.gitignore +15 -0
- data/.rspec.example +1 -0
- data/CHANGELOG +129 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +187 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +188 -0
- data/Rakefile +18 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/gemfiles/Gemfile.rails-3.2.22 +5 -0
- data/gemfiles/Gemfile.rails-4.0.x +6 -0
- data/gemfiles/Gemfile.rails-4.1.x +5 -0
- data/gemfiles/Gemfile.rails-4.2.x +5 -0
- data/gemfiles/Gemfile.rails-edge +5 -0
- data/lib/generators/rails_footnotes/install_generator.rb +14 -0
- data/lib/generators/templates/rails_footnotes.rb +26 -0
- data/lib/rails-footnotes.rb +68 -0
- data/lib/rails-footnotes/abstract_note.rb +178 -0
- data/lib/rails-footnotes/each_with_rescue.rb +36 -0
- data/lib/rails-footnotes/extension.rb +24 -0
- data/lib/rails-footnotes/filter.rb +359 -0
- data/lib/rails-footnotes/notes/all.rb +1 -0
- data/lib/rails-footnotes/notes/assigns_note.rb +60 -0
- data/lib/rails-footnotes/notes/controller_note.rb +55 -0
- data/lib/rails-footnotes/notes/cookies_note.rb +17 -0
- data/lib/rails-footnotes/notes/env_note.rb +24 -0
- data/lib/rails-footnotes/notes/files_note.rb +49 -0
- data/lib/rails-footnotes/notes/filters_note.rb +51 -0
- data/lib/rails-footnotes/notes/javascripts_note.rb +16 -0
- data/lib/rails-footnotes/notes/layout_note.rb +26 -0
- data/lib/rails-footnotes/notes/log_note.rb +47 -0
- data/lib/rails-footnotes/notes/log_note/note_logger.rb +59 -0
- data/lib/rails-footnotes/notes/params_note.rb +21 -0
- data/lib/rails-footnotes/notes/partials_note.rb +38 -0
- data/lib/rails-footnotes/notes/queries_note.rb +121 -0
- data/lib/rails-footnotes/notes/routes_note.rb +60 -0
- data/lib/rails-footnotes/notes/session_note.rb +27 -0
- data/lib/rails-footnotes/notes/stylesheets_note.rb +16 -0
- data/lib/rails-footnotes/notes/view_note.rb +41 -0
- data/lib/rails-footnotes/version.rb +3 -0
- data/lib/rails6-footnotes.rb +1 -0
- data/rails-footnotes.gemspec +22 -0
- data/spec/abstract_note_spec.rb +89 -0
- data/spec/app/assets/config/manifest.js +2 -0
- data/spec/app/assets/javascripts/foobar.js +1 -0
- data/spec/app/assets/stylesheets/foobar.css +0 -0
- data/spec/app/views/files/index.html.erb +1 -0
- data/spec/app/views/layouts/application.html.erb +12 -0
- data/spec/app/views/partials/_foo.html.erb +1 -0
- data/spec/app/views/partials/index.html.erb +1 -0
- data/spec/controllers/files_note_controller_spec.rb +38 -0
- data/spec/controllers/footnotes_controller_spec.rb +128 -0
- data/spec/controllers/log_note_controller_spec.rb +32 -0
- data/spec/controllers/partials_note_controller_spec.rb +28 -0
- data/spec/env_note_spec.rb +73 -0
- data/spec/fixtures/html_download.html +5 -0
- data/spec/footnotes_spec.rb +234 -0
- data/spec/notes/assigns_note_spec.rb +50 -0
- data/spec/notes/controller_note_spec.rb +12 -0
- data/spec/notes/files_note_spec.rb +26 -0
- data/spec/notes/javascripts_note_spec.rb +18 -0
- data/spec/notes/stylesheets_note_spec.rb +19 -0
- data/spec/notes/view_note_spec.rb +12 -0
- data/spec/spec_helper.rb +68 -0
- metadata +153 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Footnotes
|
4
|
+
module RailsFootnotesExtension
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
prepend_before_action :rails_footnotes_before_filter
|
9
|
+
after_action :rails_footnotes_after_filter
|
10
|
+
end
|
11
|
+
|
12
|
+
def rails_footnotes_before_filter
|
13
|
+
Footnotes::Filter.start!(self) if Footnotes.enabled?(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def rails_footnotes_after_filter
|
17
|
+
return unless Footnotes.enabled?(self)
|
18
|
+
|
19
|
+
filter = Footnotes::Filter.new(self)
|
20
|
+
filter.add_footnotes!
|
21
|
+
filter.close!(self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,359 @@
|
|
1
|
+
module Footnotes
|
2
|
+
class Filter
|
3
|
+
@@no_style = false
|
4
|
+
@@multiple_notes = false
|
5
|
+
@@klasses = []
|
6
|
+
@@lock_top_right = false
|
7
|
+
@@font_size = '11px'
|
8
|
+
|
9
|
+
# Default link prefix is textmate
|
10
|
+
@@prefix = 'txmt://open?url=file://%s&line=%d&column=%d'
|
11
|
+
|
12
|
+
# Edit notes
|
13
|
+
@@notes = [ :controller, :view, :layout, :partials, :stylesheets, :javascripts ]
|
14
|
+
# Show notes
|
15
|
+
@@notes += [ :assigns, :session, :cookies, :params, :filters, :routes, :env, :queries, :log]
|
16
|
+
|
17
|
+
# :no_style => If you don't want the style to be appended to your pages
|
18
|
+
# :notes => Class variable that holds the notes to be processed
|
19
|
+
# :prefix => Prefix appended to FootnotesLinks
|
20
|
+
# :multiple_notes => Set to true if you want to open several notes at the same time
|
21
|
+
# :lock_top_right => Lock a btn to toggle notes to the top right of the browser
|
22
|
+
# :font_size => CSS font-size property
|
23
|
+
cattr_accessor :no_style, :notes, :prefix, :multiple_notes, :lock_top_right, :font_size
|
24
|
+
|
25
|
+
class << self
|
26
|
+
include Footnotes::EachWithRescue
|
27
|
+
|
28
|
+
# Calls the class method start! in each note
|
29
|
+
# Sometimes notes need to set variables or clean the environment to work properly
|
30
|
+
# This method allows this kind of setup
|
31
|
+
#
|
32
|
+
def start!(controller)
|
33
|
+
self.each_with_rescue(Footnotes.before_hooks) {|hook| hook.call(controller, self)}
|
34
|
+
|
35
|
+
@@klasses = []
|
36
|
+
self.each_with_rescue(@@notes.flatten) do |note|
|
37
|
+
klass = "Footnotes::Notes::#{note.to_s.camelize}Note".constantize
|
38
|
+
klass.start!(controller) if klass.respond_to?(:start!)
|
39
|
+
@@klasses << klass
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# If none argument is sent, simply return the prefix.
|
44
|
+
# Otherwise, replace the args in the prefix.
|
45
|
+
#
|
46
|
+
def prefix(*args)
|
47
|
+
if args.empty?
|
48
|
+
@@prefix
|
49
|
+
else
|
50
|
+
args.map! { |arg| arg.to_s.split("/").map{|s| ERB::Util.url_encode(s) }.join("/") }
|
51
|
+
|
52
|
+
if @@prefix.respond_to? :call
|
53
|
+
@@prefix.call *args
|
54
|
+
else
|
55
|
+
format(@@prefix, *args)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(controller)
|
63
|
+
@controller = controller
|
64
|
+
@template = controller.instance_variable_get(:@template)
|
65
|
+
@notes = []
|
66
|
+
|
67
|
+
revert_pos(controller.response_body) do
|
68
|
+
@body = controller.response.body
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_footnotes!
|
73
|
+
add_footnotes_without_validation! if valid?
|
74
|
+
rescue Exception => e
|
75
|
+
# Discard footnotes if there are any problems
|
76
|
+
self.class.log_error("Footnotes Exception", e)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Calls the class method close! in each note
|
80
|
+
# Sometimes notes need to finish their work even after being read
|
81
|
+
# This method allows this kind of work
|
82
|
+
#
|
83
|
+
def close!(controller)
|
84
|
+
self.each_with_rescue(@@klasses) {|klass| klass.close!(controller)}
|
85
|
+
self.each_with_rescue(Footnotes.after_hooks) {|hook| hook.call(controller, self)}
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
def valid?
|
90
|
+
@body.is_a?(String) && performed_render? && valid_format? && valid_content_type? &&
|
91
|
+
!component_request? && !xhr? && !footnotes_disabled? && !attached_file?
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_footnotes_without_validation!
|
95
|
+
initialize_notes!
|
96
|
+
insert_styles unless @@no_style
|
97
|
+
insert_footnotes
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize_notes!
|
101
|
+
each_with_rescue(@@klasses) do |klass|
|
102
|
+
note = klass.new(@controller)
|
103
|
+
@notes << note if note.valid?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def revert_pos(file)
|
108
|
+
return yield unless file.respond_to?(:pos) && file.respond_to?(:pos=)
|
109
|
+
original = file.pos
|
110
|
+
yield
|
111
|
+
file.pos = original
|
112
|
+
end
|
113
|
+
|
114
|
+
def performed_render?
|
115
|
+
@controller.respond_to?(:performed?) && @controller.performed?
|
116
|
+
end
|
117
|
+
|
118
|
+
def valid_format?
|
119
|
+
format = @controller.response.content_type
|
120
|
+
format.nil? || format.include?("text/html")
|
121
|
+
end
|
122
|
+
|
123
|
+
def valid_content_type?
|
124
|
+
c = @controller.response.headers['Content-Type'].to_s
|
125
|
+
(c.empty? || c =~ /html/)
|
126
|
+
end
|
127
|
+
|
128
|
+
def component_request?
|
129
|
+
@controller.instance_variable_get(:@parent_controller)
|
130
|
+
end
|
131
|
+
|
132
|
+
def xhr?
|
133
|
+
@controller.request.xhr?
|
134
|
+
end
|
135
|
+
|
136
|
+
def footnotes_disabled?
|
137
|
+
@controller.params[:footnotes] == "false"
|
138
|
+
end
|
139
|
+
|
140
|
+
def attached_file?
|
141
|
+
!!(@controller.headers['Content-Disposition'] =~ /attachment/)
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Insertion methods
|
146
|
+
#
|
147
|
+
|
148
|
+
def insert_styles
|
149
|
+
#TODO More customizable(reset.css, from file etc.)
|
150
|
+
if @@lock_top_right
|
151
|
+
extra_styles = <<-STYLES
|
152
|
+
#footnotes_debug {position: fixed; top: 0px; right: 0px; width: 100%; z-index: 10000; margin-top: 0;}
|
153
|
+
#footnotes_debug #toggle_footnotes {position: absolute; right: 0; top: 0; background: #fff; border: 1px solid #ccc; color: #9b1b1b; font-size: 20px; text-align: center; padding: 8px; opacity: 0.9;}
|
154
|
+
#footnotes_debug #toggle_footnotes:hover {opacity: 1;}
|
155
|
+
#footnotes_debug #all_footnotes {display: none; padding: 15px; background: #fff; box-shadow: 0 0 5px rgba(0,0,0,0.4);}
|
156
|
+
#footnotes_debug fieldset > div {max-height: 500px; overflow: scroll;}
|
157
|
+
STYLES
|
158
|
+
else
|
159
|
+
extra_styles = <<-STYLES
|
160
|
+
#footnotes_debug #toggle_footnotes {display: none;}
|
161
|
+
STYLES
|
162
|
+
end
|
163
|
+
insert_text :before, /<\/head>/i, <<-HTML
|
164
|
+
<!-- Footnotes Style -->
|
165
|
+
<style type="text/css">
|
166
|
+
#footnotes_debug {font-size: #{@@font_size}; font-family: Consolas, monaco, monospace; font-weight: normal; margin: 2em 0 1em 0; text-align: center; color: #444; line-height: 16px; background: #fff;}
|
167
|
+
#footnotes_debug th, #footnotes_debug td {color: #444; line-height: 18px;}
|
168
|
+
#footnotes_debug a {color: #9b1b1b; font-weight: inherit; text-decoration: none; line-height: 18px;}
|
169
|
+
#footnotes_debug table {text-align: left; width: 100%;}
|
170
|
+
#footnotes_debug table td {padding: 5px; border-bottom: 1px solid #ccc;}
|
171
|
+
#footnotes_debug table td strong {color: #9b1b1b;}
|
172
|
+
#footnotes_debug table th {padding: 5px; border-bottom: 1px solid #ccc;}
|
173
|
+
#footnotes_debug table tr:nth-child(2n) td {background: #f5f5f5;}
|
174
|
+
#footnotes_debug table tr:nth-child(2n + 1) td {background: #fff;}
|
175
|
+
#footnotes_debug tbody {text-align: left;}
|
176
|
+
#footnotes_debug .name_values td {vertical-align: top;}
|
177
|
+
#footnotes_debug legend {background-color: #fff;}
|
178
|
+
#footnotes_debug fieldset {text-align: left; border: 1px dashed #aaa; padding: 0.5em 1em 1em 1em; margin: 1em 2em; color: #444; background-color: #FFF;}
|
179
|
+
#{extra_styles}
|
180
|
+
/* Aditional Stylesheets */
|
181
|
+
#{@notes.map(&:stylesheet).compact.join("\n")}
|
182
|
+
</style>
|
183
|
+
<!-- End Footnotes Style -->
|
184
|
+
HTML
|
185
|
+
end
|
186
|
+
|
187
|
+
def insert_footnotes
|
188
|
+
# Fieldsets method should be called first
|
189
|
+
content = fieldsets
|
190
|
+
element_style = ''
|
191
|
+
if @@lock_top_right
|
192
|
+
element_style = 'style="display: none;"'
|
193
|
+
end
|
194
|
+
footnotes_html = <<-HTML
|
195
|
+
<!-- Footnotes -->
|
196
|
+
<div style="clear:both"></div>
|
197
|
+
<div id="footnotes_debug">
|
198
|
+
<a id="toggle_footnotes" href="#" onclick="Footnotes.toggle('all_footnotes'); return false;">fn</a>
|
199
|
+
<div id="all_footnotes" #{element_style}>
|
200
|
+
#{links}
|
201
|
+
#{content}
|
202
|
+
</div>
|
203
|
+
<script type="text/javascript">
|
204
|
+
var Footnotes = function() {
|
205
|
+
|
206
|
+
function hideAll(){
|
207
|
+
#{close unless @@multiple_notes}
|
208
|
+
}
|
209
|
+
|
210
|
+
function hideAllAndToggle(id) {
|
211
|
+
var n = note(id);
|
212
|
+
var display = n.style.display;
|
213
|
+
hideAll();
|
214
|
+
// Restore original display to allow toggling
|
215
|
+
n.style.display = display;
|
216
|
+
toggle(id)
|
217
|
+
|
218
|
+
location.href = '#footnotes_debug';
|
219
|
+
}
|
220
|
+
|
221
|
+
function note(id) {
|
222
|
+
return (document.getElementById(id));
|
223
|
+
}
|
224
|
+
|
225
|
+
function toggle(id){
|
226
|
+
var el = note(id);
|
227
|
+
if (el.style.display == 'none') {
|
228
|
+
Footnotes.show(el);
|
229
|
+
} else {
|
230
|
+
Footnotes.hide(el);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
function show(element) {
|
235
|
+
element.style.display = 'block'
|
236
|
+
}
|
237
|
+
|
238
|
+
function hide(element) {
|
239
|
+
element.style.display = 'none'
|
240
|
+
}
|
241
|
+
|
242
|
+
return {
|
243
|
+
show: show,
|
244
|
+
hide: hide,
|
245
|
+
toggle: toggle,
|
246
|
+
hideAllAndToggle: hideAllAndToggle
|
247
|
+
}
|
248
|
+
}();
|
249
|
+
/* Additional Javascript */
|
250
|
+
#{@notes.map(&:javascript).compact.join("\n")}
|
251
|
+
</script>
|
252
|
+
</div>
|
253
|
+
<!-- End Footnotes -->
|
254
|
+
HTML
|
255
|
+
|
256
|
+
placeholder = /<div[^>]+id=['"]footnotes_holder['"][^>]*>/i
|
257
|
+
if @controller.response.body =~ placeholder
|
258
|
+
insert_text :after, placeholder, footnotes_html
|
259
|
+
else
|
260
|
+
insert_text :before, /<\/body>/i, footnotes_html
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Process notes to gets their links in their equivalent row
|
265
|
+
#
|
266
|
+
def links
|
267
|
+
links = Hash.new([])
|
268
|
+
order = []
|
269
|
+
each_with_rescue(@notes) do |note|
|
270
|
+
order << note.row
|
271
|
+
links[note.row] += [link_helper(note)]
|
272
|
+
end
|
273
|
+
|
274
|
+
html = ''
|
275
|
+
order.uniq!
|
276
|
+
order.each do |row|
|
277
|
+
html << "#{row.is_a?(String) ? row : row.to_s.camelize}: #{links[row].join(" | \n")}<br />"
|
278
|
+
end
|
279
|
+
html
|
280
|
+
end
|
281
|
+
|
282
|
+
# Process notes to get their content
|
283
|
+
#
|
284
|
+
def fieldsets
|
285
|
+
content = ''
|
286
|
+
each_with_rescue(@notes) do |note|
|
287
|
+
next unless note.has_fieldset?
|
288
|
+
content << <<-HTML
|
289
|
+
<fieldset id="#{note.to_sym}_debug_info" style="display: none">
|
290
|
+
<legend>#{note.legend}</legend>
|
291
|
+
<div>#{note.content}</div>
|
292
|
+
</fieldset>
|
293
|
+
HTML
|
294
|
+
end
|
295
|
+
content
|
296
|
+
end
|
297
|
+
|
298
|
+
# Process notes to get javascript code to close them.
|
299
|
+
# This method is only used when multiple_notes is false.
|
300
|
+
#
|
301
|
+
def close
|
302
|
+
javascript = ''
|
303
|
+
each_with_rescue(@notes) do |note|
|
304
|
+
next unless note.has_fieldset?
|
305
|
+
javascript << close_helper(note)
|
306
|
+
end
|
307
|
+
javascript
|
308
|
+
end
|
309
|
+
|
310
|
+
#
|
311
|
+
# Helpers
|
312
|
+
#
|
313
|
+
|
314
|
+
# Helper that creates the javascript code to close the note
|
315
|
+
#
|
316
|
+
def close_helper(note)
|
317
|
+
"Footnotes.hide(document.getElementById('#{note.to_sym}_debug_info'));\n"
|
318
|
+
end
|
319
|
+
|
320
|
+
# Helper that creates the link and javascript code when note is clicked
|
321
|
+
#
|
322
|
+
def link_helper(note)
|
323
|
+
onclick = note.onclick
|
324
|
+
unless href = note.link
|
325
|
+
href = '#'
|
326
|
+
onclick ||= "Footnotes.hideAllAndToggle('#{note.to_sym}_debug_info');return false;" if note.has_fieldset?
|
327
|
+
end
|
328
|
+
|
329
|
+
"<a href=\"#{href}\" onclick=\"#{onclick}\">#{note.title}</a>"
|
330
|
+
end
|
331
|
+
|
332
|
+
# Inserts text in to the body of the document
|
333
|
+
# +pattern+ is a Regular expression which, when matched, will cause +new_text+
|
334
|
+
# to be inserted before or after the match. If no match is found, +new_text+ is appended
|
335
|
+
# to the body instead. +position+ may be either :before or :after
|
336
|
+
#
|
337
|
+
def insert_text(position, pattern, new_text)
|
338
|
+
index = case pattern
|
339
|
+
when Regexp
|
340
|
+
if match = @controller.response.body.match(pattern)
|
341
|
+
match.offset(0)[position == :before ? 0 : 1]
|
342
|
+
else
|
343
|
+
@controller.response.body.size
|
344
|
+
end
|
345
|
+
else
|
346
|
+
pattern
|
347
|
+
end
|
348
|
+
newbody = @controller.response.body
|
349
|
+
newbody.insert index, new_text
|
350
|
+
@controller.response.body = newbody
|
351
|
+
end
|
352
|
+
|
353
|
+
# Instance each_with_rescue method
|
354
|
+
#
|
355
|
+
def each_with_rescue(*args, &block)
|
356
|
+
self.class.each_with_rescue(*args, &block)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), '*_note.rb')].each {|note| require note}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Footnotes
|
2
|
+
module Notes
|
3
|
+
class AssignsNote < AbstractNote
|
4
|
+
@@ignored_assigns = [
|
5
|
+
:@real_format,
|
6
|
+
:@before_filter_chain_aborted,
|
7
|
+
:@performed_redirect,
|
8
|
+
:@performed_render,
|
9
|
+
:@_params,
|
10
|
+
:@_response,
|
11
|
+
:@url,
|
12
|
+
:@template,
|
13
|
+
:@_request,
|
14
|
+
:@db_rt_before_render,
|
15
|
+
:@db_rt_after_render,
|
16
|
+
:@view_runtime,
|
17
|
+
:@marked_for_same_origin_verification
|
18
|
+
]
|
19
|
+
cattr_accessor :ignored_assigns, :instance_writer => false
|
20
|
+
@@ignored_assigns_pattern = /^@_/
|
21
|
+
cattr_accessor :ignored_assigns_pattern, :instance_writer => false
|
22
|
+
|
23
|
+
def initialize(controller)
|
24
|
+
@controller = controller
|
25
|
+
end
|
26
|
+
|
27
|
+
def title
|
28
|
+
"Assigns (#{assigns.size})"
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid?
|
32
|
+
assigns.present?
|
33
|
+
end
|
34
|
+
|
35
|
+
def content
|
36
|
+
mount_table(to_table, :summary => "Debug information for #{title}")
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
def to_table
|
41
|
+
table = assigns.inject([]) do |rr, var|
|
42
|
+
class_name = assigned_value(var).class.name
|
43
|
+
var_name = var.to_s
|
44
|
+
rr << ["<strong>#{var.to_s}</strong>" + "<br /><em>#{class_name}</em>", escape(assigned_value(var).inspect)]
|
45
|
+
end
|
46
|
+
|
47
|
+
table.unshift(['Name', 'Value'])
|
48
|
+
end
|
49
|
+
|
50
|
+
def assigns
|
51
|
+
@assigns ||= @controller.instance_variables.map {|v| v.to_sym}.select {|v| v.to_s !~ ignored_assigns_pattern } - ignored_assigns
|
52
|
+
end
|
53
|
+
|
54
|
+
def assigned_value(key)
|
55
|
+
@controller.instance_variable_get(key)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Footnotes
|
2
|
+
module Notes
|
3
|
+
class ControllerNote < AbstractNote
|
4
|
+
def initialize(controller)
|
5
|
+
@controller = controller
|
6
|
+
end
|
7
|
+
|
8
|
+
def row
|
9
|
+
:edit
|
10
|
+
end
|
11
|
+
|
12
|
+
def link
|
13
|
+
Footnotes::Filter.prefix(controller_filename, controller_line_number + 1, 3)
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
prefix? && controller_filename && File.exists?(controller_filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def controller_path
|
22
|
+
@controller_path = @controller.class.name.underscore
|
23
|
+
end
|
24
|
+
|
25
|
+
def controller_filename
|
26
|
+
@controller_filename ||= Gem.find_files(controller_path).first # tnx https://github.com/MasterLambaster
|
27
|
+
end
|
28
|
+
|
29
|
+
def controller_text
|
30
|
+
@controller_text ||= IO.read(controller_filename)
|
31
|
+
end
|
32
|
+
|
33
|
+
def action_index
|
34
|
+
(controller_text =~ /def\s+#{@controller.action_name}[\s\(]/)
|
35
|
+
end
|
36
|
+
|
37
|
+
def controller_line_number
|
38
|
+
lines_from_index(controller_text, action_index) || 0
|
39
|
+
end
|
40
|
+
|
41
|
+
def lines_from_index(string, index)
|
42
|
+
return nil if string.blank? || index.blank?
|
43
|
+
|
44
|
+
lines = string.respond_to?(:to_a) ? string.to_a : string.lines.to_a
|
45
|
+
running_length = 0
|
46
|
+
lines.each_with_index do |line, i|
|
47
|
+
running_length += line.length
|
48
|
+
if running_length > index
|
49
|
+
return i
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|