lurker 0.6.8 → 1.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.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/.hound.yml +4 -78
  3. data/.jshintrc +33 -0
  4. data/.rspec +1 -2
  5. data/.rubocop.yml +0 -1
  6. data/.travis.yml +30 -19
  7. data/Gemfile +0 -33
  8. data/README.md +32 -53
  9. data/Rakefile +3 -3
  10. data/cucumber.yml +1 -2
  11. data/features/atom_persistent_within_the_same_type.feature +4 -4
  12. data/features/controller_nested_schema_scaffolding.feature +7 -10
  13. data/features/controller_schema_scaffolding.feature +1 -3
  14. data/features/dereferencing_through_inlining.feature +1 -3
  15. data/features/html_generation.feature +26 -4
  16. data/features/minitest.feature +3 -8
  17. data/features/multidomain_support.feature +6 -10
  18. data/features/multitype_request_support.feature +1 -3
  19. data/features/partials.feature +3 -8
  20. data/features/request_nested_schema_scaffolding.feature +0 -2
  21. data/features/request_schema_scaffolding.feature +0 -2
  22. data/features/schema_suffixes.feature +2 -18
  23. data/features/schema_updating_within_test_suite.feature +2 -6
  24. data/features/step_definitions/additional_cli_steps.rb +16 -12
  25. data/features/support/env.rb +50 -10
  26. data/features/test_endpoint.feature +2 -9
  27. data/gemfiles/rails_4.gemfile +14 -0
  28. data/gemfiles/rails_5.gemfile +10 -0
  29. data/gemfiles/rails_6.gemfile +10 -0
  30. data/lib/lurker.rb +9 -2
  31. data/lib/lurker/cli.rb +148 -128
  32. data/lib/lurker/endpoint.rb +11 -7
  33. data/lib/lurker/form_builder.rb +22 -31
  34. data/lib/lurker/json/concerns/validatable.rb +5 -1
  35. data/lib/lurker/json/parser.rb +1 -1
  36. data/lib/lurker/json/schema.rb +19 -6
  37. data/lib/lurker/json/{writter.rb → writer.rb} +2 -2
  38. data/lib/lurker/presenters/base_presenter.rb +10 -34
  39. data/lib/lurker/presenters/endpoint_presenter.rb +19 -8
  40. data/lib/lurker/presenters/schema_presenter.rb +6 -5
  41. data/lib/lurker/presenters/service_presenter.rb +41 -7
  42. data/lib/lurker/rendering_controller.rb +14 -7
  43. data/lib/lurker/service.rb +27 -11
  44. data/lib/lurker/spec_helper/rspec.rb +0 -4
  45. data/lib/lurker/spy.rb +3 -1
  46. data/lib/lurker/templates/documentation.md.tt +1 -0
  47. data/lib/lurker/templates/javascripts/lurker.js +133 -91
  48. data/lib/lurker/templates/layouts/_sidemenu.html.erb +2 -2
  49. data/lib/lurker/templates/layouts/application.html.erb +54 -57
  50. data/lib/lurker/templates/layouts/print.html.erb +31 -0
  51. data/lib/lurker/templates/lurker/rendering/_endpoint.html.erb +37 -0
  52. data/lib/lurker/templates/lurker/rendering/_param_form_element.html.erb +1 -1
  53. data/lib/lurker/templates/lurker/rendering/_service.html.erb +7 -0
  54. data/lib/lurker/templates/lurker/rendering/_submit_form.html.erb +77 -73
  55. data/lib/lurker/templates/lurker/rendering/all.html.erb +5 -0
  56. data/lib/lurker/templates/lurker/rendering/index.html.erb +1 -10
  57. data/lib/lurker/templates/lurker/rendering/show.html.erb +1 -37
  58. data/lib/lurker/templates/public/application.css +6 -2
  59. data/lib/lurker/templates/public/application.js +13 -13
  60. data/lib/lurker/templates/stylesheets/application.scss +3 -0
  61. data/lib/lurker/version.rb +1 -1
  62. data/lurker.gemspec +31 -33
  63. data/spec/spec_helper.rb +0 -1
  64. data/tasks/build.rake +12 -8
  65. data/tasks/generate.rake +44 -17
  66. data/templates/Dockerfile +26 -0
  67. data/templates/generate_stuff.rb +59 -26
  68. data/templates/lurker_app.rb +27 -48
  69. data/templates/rails4_ruby26_thread_error_fix.rb +20 -0
  70. metadata +149 -106
  71. checksums.yaml.gz.sig +0 -2
  72. data.tar.gz.sig +0 -3
  73. data/Appraisals +0 -20
  74. data/gemfiles/rails_32.gemfile +0 -27
  75. data/gemfiles/rails_40.gemfile +0 -27
  76. data/gemfiles/rails_41.gemfile +0 -27
  77. data/gemfiles/rails_42.gemfile +0 -27
  78. data/lib/lurker/templates/lurker/rendering/_param_form_legend.html.erb +0 -1
  79. data/lib/lurker/templates/meta_service.md.erb +0 -20
  80. data/lib/lurker/validation_error.rb +0 -4
  81. data/templates/rails32_http_patch_support.rb +0 -125
  82. metadata.gz.sig +0 -0
@@ -2,28 +2,30 @@ require 'ostruct'
2
2
  require 'abstract_controller'
3
3
  require 'action_view'
4
4
  require 'action_dispatch/http/mime_type'
5
+ require 'action_dispatch/routing'
5
6
 
6
7
  module Lurker
7
8
  class RenderingController < ::AbstractController::Base
8
9
  # Include all the concerns we need to make this work
10
+ include AbstractController::Logger
9
11
  include AbstractController::Helpers
10
12
  include AbstractController::Rendering
11
- include ActionView::Rendering if defined?(ActionView::Rendering)
12
- include ActionView::Layouts if defined?(ActionView::Layouts) # Rails 4.1.x
13
+ include AbstractController::AssetPaths
13
14
  include AbstractController::Layouts if defined?(AbstractController::Layouts) # Rails 3.2.x, 4.0.x
15
+ include ActionView::Layouts if defined?(ActionView::Layouts) # Rails 4.1.x
16
+ include ActionView::Rendering if defined?(ActionView::Rendering)
14
17
  include ActionView::Context
15
18
 
16
- self.view_paths = File.join(File.dirname(__FILE__), "templates")
19
+ attr_writer :service_presenter, :endpoint_presenter
17
20
 
18
21
  # Define additional helpers, this one is for csrf_meta_tag
19
- helper_method :protect_against_forgery?, :tag_with_anchor
22
+ helper_method :title, :tag_with_anchor, :protect_against_forgery?
20
23
 
21
24
  # override the layout in your subclass if needed.
22
25
  layout 'application'
23
26
 
24
- # we are not in a browser, no need for this
25
- def protect_against_forgery?
26
- false
27
+ def title
28
+ [@service_presenter.try(:title), @endpoint_presenter.try(:title)].compact.join ' | '
27
29
  end
28
30
 
29
31
  def tag_with_anchor(tag, content, anchor_slug = nil)
@@ -37,6 +39,11 @@ module Lurker
37
39
  EOS
38
40
  end
39
41
 
42
+ # we are not in a browser, no need for this
43
+ def protect_against_forgery?
44
+ false
45
+ end
46
+
40
47
  # so that your flash calls still work
41
48
  def flash
42
49
  {}
@@ -3,8 +3,17 @@ require 'yaml'
3
3
  # Services represent a group of Lurker API endpoints in a directory
4
4
  class Lurker::Service
5
5
  attr_reader :service_dir, :schema
6
+ attr_writer :documentation
6
7
  attr_accessor :opened_endpoints
7
8
  SUFFIX = '.service.yml'
9
+ DEFAULT_SCHEMA = {
10
+ 'name' => '',
11
+ 'basePath' => '',
12
+ 'description' => '',
13
+ 'domains' => {},
14
+ 'consumes' => %w(application/x-www-form-urlencode application/json),
15
+ 'produces' => %w(application/json)
16
+ }
8
17
 
9
18
  def self.default_service
10
19
  new(Lurker.service_path)
@@ -15,14 +24,9 @@ class Lurker::Service
15
24
  @service_dir = File.expand_path(service_dir)
16
25
  @service_filename = service_name
17
26
  @schema = if persisted? && (schema = YAML.load_file(service_path)).is_a?(Hash)
18
- Lurker::Json::Schema.new(schema)
27
+ Lurker::Json::Schema.new(schema.stringify_keys, uri: service_path)
19
28
  else
20
- Lurker::Json::Schema.new(
21
- 'name' => service_filename,
22
- 'basePath' => '',
23
- 'description' => '',
24
- 'domains' => {}
25
- )
29
+ Lurker::Json::Schema.new(DEFAULT_SCHEMA.merge('name' => service_filename), uri: service_path)
26
30
  end
27
31
  end
28
32
 
@@ -39,7 +43,7 @@ class Lurker::Service
39
43
  end
40
44
 
41
45
  def persist!
42
- Lurker::Json::Writter.write(schema, service_path) unless File.exist?(service_path)
46
+ Lurker::Json::Writer.write(schema, service_path) unless File.exist?(service_path)
43
47
  @opened_endpoints.each { |ep| ep.persist! if ep.respond_to?(:persist!) }
44
48
  end
45
49
 
@@ -97,19 +101,31 @@ class Lurker::Service
97
101
  if base_path && !base_path.end_with?('/')
98
102
  base_path + '/'
99
103
  else
100
- base_path
104
+ base_path || '/'
101
105
  end
102
106
  end
103
107
 
104
108
  def description
105
- @schema['description']
109
+ schema['description']
106
110
  end
107
111
 
108
112
  def discussion
109
- @schema['discussion']
113
+ schema['discussion']
110
114
  end
111
115
 
112
116
  def domains
113
117
  schema['domains']
114
118
  end
119
+
120
+ def request_media_types
121
+ schema['consumes']
122
+ end
123
+
124
+ def response_media_types
125
+ schema['produces']
126
+ end
127
+
128
+ def documentation
129
+ @documentation ||= schema.documentation
130
+ end
115
131
  end
@@ -16,9 +16,5 @@ if defined?(RSpec) && RSpec.respond_to?(:configure)
16
16
  end
17
17
  Lurker::Spy.on(options, &example)
18
18
  end
19
-
20
- unless RSpec::Core::Version::STRING > '3'
21
- config.treat_symbols_as_metadata_keys_with_true_values = true
22
- end
23
19
  end
24
20
  end
@@ -16,7 +16,9 @@ module Lurker
16
16
  @block = block
17
17
 
18
18
  @service = if defined?(Rails)
19
- Service.new(Rails.root.join(DEFAULT_SERVICE_PATH).to_s, Rails.application.class.parent_name)
19
+ rails_app_class = Rails.application.class
20
+ rails_app_name = rails_app_class.respond_to?(:module_parent_name) ? rails_app_class.module_parent_name : rails_app_class.parent_name
21
+ Service.new(Rails.root.join(DEFAULT_SERVICE_PATH).to_s, rails_app_name)
20
22
  else
21
23
  Service.default_service
22
24
  end
@@ -0,0 +1 @@
1
+ This is an autogenerated documentation stub. You can edit it in: `<%= config[:path] %>`
@@ -1,101 +1,143 @@
1
- var window = this.window;
2
- var $ = window.$;
3
- var Lurker = {
4
-
5
- disableSubmitButton: function() {
6
- $("#submit-api").attr("disabled", true);
7
- },
8
-
9
- enableSubmitButton: function() {
10
- $("#submit-api").attr("disabled", false);
11
- },
1
+ (function() {
2
+ var $ = window.jQuery;
3
+ var hljs = window.hljs;
4
+
5
+ var Lurker = {
6
+ disableSubmitButton: function() {
7
+ $("#submit-api").attr("disabled", true);
8
+ },
9
+
10
+ enableSubmitButton: function() {
11
+ $("#submit-api").attr("disabled", false);
12
+ },
13
+
14
+ fillInInfoTab: function($tab, xhr) {
15
+ $tab.find('.status td.value').text(xhr.status + " " + xhr.statusText);
16
+ $tab.find('#headers').text(xhr.getAllResponseHeaders());
17
+
18
+ var realTimeTaken = Lurker.lastRequest.endTime - Lurker.lastRequest.startTime;
19
+ $tab.find('.time td.value').text(realTimeTaken + " ms");
20
+ },
21
+
22
+ fillInRawTab: function($tab, xhr) {
23
+ var content;
24
+ switch (Lurker.detectContentType(xhr)) {
25
+ case "json":
26
+ var json = JSON.stringify(JSON.parse(xhr.responseText), null, 2);
27
+ content = hljs.highlightAuto(json).value;
28
+ break;
29
+ default:
30
+ content = xhr.responseText;
31
+ }
32
+ $tab.html(content);
33
+ },
34
+
35
+ detectContentType: function(response) {
36
+ var contentType = response.getResponseHeader("Content-Type");
37
+ var detectedContentType = null;
38
+
39
+ if (contentType.match(/application\/json/)) {
40
+ detectedContentType = 'json';
41
+ }
42
+
43
+ return detectedContentType;
44
+ },
45
+
46
+ buildActionUrl: function(host, template, values) {
47
+ for (var i = 0; i < values.length; i++) {
48
+ var placeholder = new RegExp(':' + values[i].label);
49
+ template = template.replace(placeholder, encodeURIComponent(values[i].value));
50
+ }
51
+ return host + template;
52
+ },
53
+
54
+ serializePayload: function(payload, contentType) {
55
+ if (contentType === 'application/json' || /\+json$/.test(contentType)) {
56
+ return JSON.stringify(payload);
57
+ } else {
58
+ // default to 'application/x-www-form-urlencoded'
59
+ return jQuery.param(payload);
60
+ }
61
+ },
62
+
63
+ generateCurlCommand: function(targetUrl, method, payload, contentType) {
64
+ var serializedPayload = Lurker.serializePayload(payload, contentType);
65
+ var results = [];
66
+ results.push('curl');
67
+ results.push('-X ' + method);
68
+ if (serializedPayload.length > 0) {
69
+ if (contentType != 'application/x-www-form-urlencoded') {
70
+ results.push("-H 'Content-Type: " + contentType + "'");
71
+ }
72
+ results.push("-d '" + serializedPayload + "'");
73
+ }
74
+ results.push("'" + targetUrl + "'");
75
+ return results.join(' ');
76
+ },
77
+
78
+ performRequest: function(host, method, template, values, payload, contentType) {
79
+ Lurker.disableSubmitButton();
80
+
81
+ $.ajax({
82
+ url: Lurker.buildActionUrl(host, template, values),
83
+ data: Lurker.serializePayload(payload, contentType),
84
+ method: method,
85
+ contentType: contentType,
86
+ processData: false
87
+ }).complete(Lurker.onComplete);
88
+
89
+ Lurker.lastRequest = {};
90
+ Lurker.lastRequest.startTime = Date.now();
91
+
92
+ return false;
93
+ },
94
+
95
+ onComplete: function(xhr) {
96
+ Lurker.lastRequest.endTime = Date.now();
97
+ Lurker.enableSubmitButton();
98
+
99
+ var $responseDiv = $("#show-api-response-div");
100
+ $responseDiv.find("[ref^='response']").hide();
101
+
102
+ Lurker.fillInInfoTab($responseDiv.showNavTab("info"), xhr);
103
+ Lurker.fillInRawTab($responseDiv.showNavTab("raw"), xhr);
104
+ }
105
+ };
12
106
 
13
- detectContentType: function(response) {
14
- var contentType = response.getResponseHeader("Content-Type");
15
- var detectedContentType = null;
107
+ window.Lurker = Lurker;
16
108
 
17
- if (contentType.match(/application\/json/)) {
18
- detectedContentType = 'json';
109
+ $(function($) {
110
+ var activeMenuItem = $('#side-menu').find('a[href="' + window.location.pathname + '"]');
111
+ if (activeMenuItem.length === 1) {
112
+ activeMenuItem.addClass('hovered')
113
+ .parents('.collapse').addClass('in')
114
+ .parents('.endpoint-group').addClass('active');
19
115
  }
20
116
 
21
- return detectedContentType;
22
- },
23
-
24
- fillInInfoTab: function($tab, xhr) {
25
- $tab.find('.status td.value').text(xhr.status + " " + xhr.statusText);
26
- $tab.find('#headers').text(xhr.getAllResponseHeaders());
27
-
28
- var realTimeTaken = Lurker.lastRequest.endTime - Lurker.lastRequest.startTime;
29
- $tab.find('.time td.value').text(realTimeTaken + " ms");
30
- },
31
-
32
- fillInRawTab: function($tab, xhr) {
33
- switch (Lurker.detectContentType(xhr)) {
34
- case "json":
35
- var json = JSON.stringify(JSON.parse(xhr.responseText), null, 2);
36
- var content = hljs.highlightAuto(json).value;
37
- break;
38
- default:
39
- var content = xhr.responseText;
117
+ window.domain = window.localStorage.lastDomain || '/';
118
+ window.domainName = window.localStorage.lastDomainName || 'Local';
119
+ function currentDomain(domain, domainName) {
120
+ $('.domains').find('.current')
121
+ .text(domainName)
122
+ .data('domain', domain);
40
123
  }
41
- $tab.html(content);
42
- },
43
-
44
- onComplete: function(xhr) {
45
- Lurker.lastRequest.endTime = Date.now();
46
- Lurker.enableSubmitButton();
47
-
48
- $("#show-api-response-div [ref^='response']").hide();
49
-
50
- Lurker.fillInInfoTab($("#show-api-response-div").showNavTab("info"), xhr);
51
- Lurker.fillInRawTab($("#show-api-response-div").showNavTab("raw"), xhr);
52
- },
53
-
54
- onSubmit: function($form) {
55
- Lurker.disableSubmitButton();
56
-
57
- $.ajax({
58
- url: $form.attr('action'),
59
- method: $form.attr('method'),
60
- data: $form.serialize()
61
- }).complete(Lurker.onComplete);
62
-
63
- Lurker.lastRequest = {};
64
- Lurker.lastRequest.startTime = Date.now();
65
-
66
- return false;
67
- }
68
- };
69
-
70
- $(function($) {
71
- var activeMenuItem = $('#side-menu a[href="' + window.location.pathname + '"]');
72
- if (activeMenuItem.length === 1) {
73
- activeMenuItem.addClass('hovered').parents('.collapse').addClass('in').parents('.endpoint-group').addClass('active');
74
- }
75
-
76
- window.domain = window.localStorage.lastDomain || '/';
77
- window.domainName = window.localStorage.lastDomainName || 'Local';
78
- function currentDomain(domain, domainName) {
79
- $('.domains').find('.current')
80
- .text(domainName)
81
- .data('domain', domain);
82
- }
83
-
84
- $('.domains .domain').click(function() {
85
- window.domain = window.localStorage.lastDomain = $(this).data('domain');
86
- window.domainName = window.localStorage.lastDomainName = $(this).text() + " (" + window.domain + ")";
124
+
125
+ $('.domains .domain').click(function() {
126
+ window.domain = window.localStorage.lastDomain = $(this).data('domain');
127
+ window.domainName = window.localStorage.lastDomainName = $(this).text() + " (" + window.domain + ")";
128
+ currentDomain(window.domain, window.domainName);
129
+ });
130
+
131
+ $('.domains .current.btn').click(function() {
132
+ window.location = $(this).data('domain');
133
+ });
87
134
  currentDomain(window.domain, window.domainName);
88
135
  });
89
136
 
90
- $('.domains .current.btn').click(function() {
91
- window.location = $(this).data('domain');
137
+ $.fn.extend({
138
+ showNavTab: function(name) {
139
+ return $(this).find("[ref=response-" + name + "]").show();
140
+ }
92
141
  });
93
- currentDomain(window.domain, window.domainName);
94
- });
95
-
96
- $.fn.extend({
97
- showNavTab: function(name) {
98
- return $(this).find("[ref=response-" + name + "]").show();
99
- },
100
- });
142
+ })();
101
143
 
@@ -5,9 +5,9 @@
5
5
  <% endpoints.each do |endpoint| %>
6
6
  <li class="<%= (@endpoint_presenter.nil? && 'opened') || (endpoint.url == @endpoint_presenter.url && 'active') %>">
7
7
  <a href="<%= endpoint.url %>">
8
- <span class="btn btn-<%= endpoint.verb_colorname %> btn-xs"><%= endpoint.verb %>&nbsp;<%= endpoint.named_path %></span>
9
- <br>
10
8
  <span><%= endpoint.title %></span>
9
+ <br>
10
+ <span class="btn btn-<%= endpoint.verb_colorname %> btn-xs"><%= endpoint.verb %>&nbsp;<%= endpoint.named_path %></span>
11
11
  </a>
12
12
  </li>
13
13
  <% end %>
@@ -1,70 +1,67 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
-
4
3
  <head>
5
-
6
- <meta charset="utf-8">
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
-
9
- <title><%= @service_presenter.title %></title>
10
- <link href="<%= @service_presenter.html_directory %>/application-<%= @service_presenter.options['css'] %>.css" media="all" rel="stylesheet">
11
- <style type="text/css">
12
- @font-face {
13
- font-family: 'FontAwesome';
14
- src: url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.eot?v=4.0.3");
15
- src: url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.eot?#iefix&v=4.0.3") format("embedded-opentype"),
16
- url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.woff?v=4.0.3") format("woff"),
17
- url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.ttf?v=4.0.3") format("truetype"),
18
- url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular") format("svg");
19
- font-weight: normal;
20
- font-style: normal;
21
- }
22
- </style>
23
-
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= title %></title>
7
+ <link href="<%= @service_presenter.asset_path('application.css') %>" media="all" rel="stylesheet">
8
+ <style type="text/css">
9
+ @font-face {
10
+ font-family: 'FontAwesome';
11
+ src: url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.eot?v=4.0.3' %>");
12
+ src: url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.eot?#iefix&v=4.0.3' %>") format("embedded-opentype"),
13
+ url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.woff?v=4.0.3' %>") format("woff"),
14
+ url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.ttf?v=4.0.3' %>") format("truetype"),
15
+ url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular' %>") format("svg");
16
+ font-weight: normal;
17
+ font-style: normal;
18
+ }
19
+ </style>
24
20
  </head>
25
-
26
21
  <body>
27
22
 
28
- <header class="navbar navbar-static-top bs-docs-nav" id="top" role="banner">
29
- <div class="container">
30
- <div class="navbar-header">
31
- <a class="navbar-brand" href="<%= @service_presenter.html_directory %>"><%= @service_presenter.name %></a>
32
- </div>
33
- </div>
34
- </header>
23
+ <header class="navbar navbar-static-top bs-docs-nav" id="top" role="banner">
24
+ <div class="container">
25
+ <div class="navbar-header">
26
+ <a class="navbar-brand" href="<%= @service_presenter.html_directory %>"><%= @service_presenter.name %></a>
27
+ </div>
28
+ </div>
29
+ </header>
35
30
 
36
- <div class="container bs-docs-container">
37
- <div class="row">
38
- <div class="col-md-9" role="main">
39
- <div class="bs-docs-section">
40
- <%= yield %>
41
- </div>
42
- </div>
43
- <div class="col-md-3">
44
- <div class="bs-docs-sidebar hidden-print" role="complementary">
45
- <ul class="nav bs-docs-sidenav">
46
- <%= render 'layouts/sidemenu' %>
47
- </ul>
48
- </div>
49
- </div>
31
+ <div class="container bs-docs-container">
32
+ <div class="row">
33
+ <div class="col-md-9" role="main">
34
+ <div class="bs-docs-section">
35
+ <%= yield %>
50
36
  </div>
51
37
  </div>
52
-
53
- <footer class="bs-docs-footer" role="contentinfo">
54
- <div class="container">
55
- <ul class="bs-docs-footer-links muted">
56
- <li>·</li>
57
- <% if @service_presenter.options[:footer].present? %>
58
- <li><%= @service_presenter.name %>&nbsp;<%= @service_presenter.options[:footer] %></li>
59
- <li>·</li>
60
- <% end %>
61
- <% if @service_presenter.options[:lurker].present? %>
62
- <li><%= @service_presenter.options[:lurker] %></li>
63
- <li>·</li>
64
- <% end %>
38
+ <div class="col-md-3">
39
+ <div class="bs-docs-sidebar hidden-print" role="complementary">
40
+ <ul class="nav bs-docs-sidenav">
41
+ <%= render 'layouts/sidemenu' %>
65
42
  </ul>
66
43
  </div>
67
- </footer>
68
- <script src="<%= @service_presenter.html_directory %>/application-<%= @service_presenter.options['js'] %>.js"></script>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <footer class="bs-docs-footer" role="contentinfo">
49
+ <div class="container">
50
+ <ul class="bs-docs-footer-links muted">
51
+ <li>·</li>
52
+ <% if @service_presenter.footer.present? %>
53
+ <li><%= raw @service_presenter.footer %></li>
54
+ <li>·</li>
55
+ <% end %>
56
+ <% if @service_presenter.lurker.present? %>
57
+ <li><%= raw @service_presenter.lurker %></li>
58
+ <li>·</li>
59
+ <% end %>
60
+ </ul>
61
+ </div>
62
+ </footer>
63
+
64
+ <script src="<%= @service_presenter.asset_path 'application.js' %>"></script>
65
+
69
66
  </body>
70
67
  </html>