intranet-core 2.3.1 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db45417345875ffcbd1500ce99febcb8e52cdd55934c44812310e75d8dbd85a1
4
- data.tar.gz: e330a7b69c123a4c380682dbf3a39ff3a92365f8d0bdcf4ceaa1e175a74ad497
3
+ metadata.gz: e9d5ce66790b8b308a7f4d366aaa0a24a4d8e4c8ea82d31c6a5b78dd93fb2a49
4
+ data.tar.gz: eaff940db100fbebae0ebc6fd7c204ce6a33117e7b2c779a5f4961da162a230f
5
5
  SHA512:
6
- metadata.gz: 360437f3351b25f886a44da9232ab7d844f4fcfcc25d1030b92ea3f6b24370613a3bc9a0dc3fc80ac000df6365a4cdc39da4c85f50ed3351603a345797e1e842
7
- data.tar.gz: ad870fc6849c997b62d60618828c292087d3a0232202d340bd18772dda6241f11ccd86a6bd91589f66f3e1cbd67165356728a0e552baf0019f30bc207c10a1d2
6
+ metadata.gz: b48372ac7f9d356b60685ac561aee1d3b3a596c188493082a8107194c0d08f9230b654796057a83a2b463138baee381045263971f7fc063ff20ffcfcf3b91c5d
7
+ data.tar.gz: 0f6ea7adb342e03243ef249a17773f7bf4db6593a09386888451948103b7730e4cc0244173bd397befb4960301d9f3a33cffc00839b273444538f714af927f31
@@ -37,30 +37,44 @@ module Intranet
37
37
  # nothing to do
38
38
  end
39
39
 
40
- # Generates the HTML content associated to the given +path+ and +query+.
40
+ # Generates the HTML answer (HTTP return code, MIME type and body) associated to the given
41
+ # +path+ and +query+.
42
+ #
43
+ # The function may return a partial content, in which case the HTTP return code must be 206 and
44
+ # the answer body is expected to be a +Hash+ with the following keys:
45
+ # * +:title+ (mandatory): the page title
46
+ # * +:content+ (mandatory): the partial content
47
+ # * +:stylesheets+ (optional): an array of the required Cascade Style Sheets (CSS) files, either
48
+ # absolute or relative to the module root
49
+ # * +:scripts+ (optional): an array of hashes, each representing a <script> element to be
50
+ # included in the generated page. Keys are the one accepted by the HTML <script> tag. The
51
+ # +:src+ key may be either absolute or relative to the module root
41
52
  # @param path [String] The requested URI, relative to that module root URI
42
53
  # @param query [Hash] The URI variable/value pairs, if any
43
- # @return [Array] The HTTP return code, the MIME type and the answer body.
54
+ # @return [Array<Integer, String, String> or Array<Integer, String, Hash>] The HTTP return code,
55
+ # the MIME type and the answer body (partial if return code is 206).
44
56
  def generate_page(path, query)
45
57
  [404, '', '']
46
58
  end
47
59
 
48
60
  # Provides the list of Cascade Style Sheets (CSS) dependencies for this module, either using
49
61
  # absolute or relative (from the module root) paths.
50
- # If redefined, this method should probably append dependencies rather than overwriting them.
62
+ # @deprecated Use {generate_page} partial content feature, setting +:stylesheets+ attribute of
63
+ # the returned body.
51
64
  # @return [Array] The list of CSS dependencies, as absolute path or relative to the module root
52
65
  # URL.
53
66
  def css_dependencies
54
- ['/design/style.css']
67
+ []
55
68
  end
56
69
 
57
70
  # Provides the list of Javascript files (JS) dependencies for this module, either using
58
71
  # absolute or relative (from the module root) paths.
59
- # If redefined, this method should probably append dependencies rather than overwriting them.
72
+ # @deprecated Use {generate_page} partial content feature, setting +:scripts+ attribute of the
73
+ # returned body.
60
74
  # @return [Array] The list of JS dependencies, as absolute path or relative to the module root
61
75
  # URL.
62
76
  def js_dependencies
63
- ['/design/nav.js']
77
+ []
64
78
  end
65
79
  end
66
80
  end
@@ -117,8 +117,9 @@ module Intranet
117
117
  # @param responder [Intranet::AbstractResponder] The responder that produced the body
118
118
  # @param path [String] The path to the responder
119
119
  def add_header_and_footer(body, responder, path)
120
- to_markup('skeleton', body: body, css: responder.css_dependencies,
121
- js: responder.js_dependencies, current_path: path)
120
+ body[:stylesheets] ||= responder.css_dependencies
121
+ body[:scripts] ||= responder.js_dependencies.map { |url| { src: url, defer: 'defer' } }
122
+ to_markup('skeleton', body: body, current_path: path)
122
123
  end
123
124
 
124
125
  # Check whether a path is valid. In particular, this function excludes a path containing an
@@ -6,7 +6,7 @@ module Intranet
6
6
  NAME = 'intranet-core'
7
7
 
8
8
  # The version of the gem, according to semantic versionning.
9
- VERSION = '2.3.1'
9
+ VERSION = '2.4.1'
10
10
 
11
11
  # The URL of the gem homepage.
12
12
  HOMEPAGE_URL = 'https://rubygems.org/gems/intranet-core'
data/lib/intranet/core.rb CHANGED
@@ -91,10 +91,11 @@ module Intranet
91
91
  # See https://github.com/nahi/webrick/blob/master/lib/webrick/accesslog.rb#L69
92
92
  ACCESSLOG_FMT = "%h '%r' -> %s (%b bytes in %Ts)"
93
93
 
94
- def load_http_server(preferred_port)
94
+ def load_http_server(preferred_port) # rubocop:disable Metrics/MethodLength
95
95
  @port = preferred_port
96
96
  begin
97
- WEBrick::HTTPServer.new(Port: @port, Logger: @logger, AccessLog: [[@logger, ACCESSLOG_FMT]])
97
+ WEBrick::HTTPServer.new(Port: @port, Logger: @logger, AccessLog: [[@logger, ACCESSLOG_FMT]],
98
+ ServerSoftware: nil)
98
99
  rescue Errno::EACCES # not enough permission to use port 80
99
100
  @port = 8080
100
101
  retry
@@ -3,24 +3,23 @@
3
3
  %head
4
4
  %title= Socket.gethostname.capitalize + ' | ' + body[:title]
5
5
  %meta{charset: 'utf-8'}
6
+ %meta{name: 'viewport', content: 'width=device-width, initial-scale=1.0'}
6
7
  %link{rel: 'icon', type: 'image/x-icon', href: '/design/favicon.ico'}
7
- - css.each do |url|
8
- - if url.start_with?('/')
9
- %link{rel: 'stylesheet', type: 'text/css', href: url}
10
- - else
11
- %link{rel: 'stylesheet', type: 'text/css', href: current_path + '/' + url}
12
- - js.each do |url|
13
- - if url.start_with?('/')
14
- %script{src: url, defer: 'defer'}
15
- - else
16
- %script{src: current_path + '/' + url, defer: 'defer'}
8
+ %link{rel: 'stylesheet', type: 'text/css', href: '/design/style.css'}
9
+ - body[:stylesheets].each do |url|
10
+ - url = current_path + '/' + url unless url.start_with?('/')
11
+ %link{rel: 'stylesheet', type: 'text/css', href: url}
12
+ %script{type: 'module', src: '/design/nav.js'}
13
+ - body[:scripts].each do |script|
14
+ - script[:src] = current_path + '/' + script[:src] unless script[:src].start_with?('/')
15
+ %script{script}
17
16
  %body
18
17
  %header
19
- %a{id: 'openmenu', onclick: 'openNavMenu();'}= '&#9776;'
18
+ %a{id: 'openmenu'}= '&#9776;'
20
19
  %h1
21
20
  %a{href: '/index.html', title: I18n.t('nav.back.home')}= Socket.gethostname.capitalize
22
21
  %nav
23
- %a{id: 'closemenu', onclick: 'closeNavMenu();'}= '&times;'
22
+ %a{id: 'closemenu'}= '&times;'
24
23
  %ul
25
24
  - responders.children_nodes.each do |path, node|
26
25
  - if node.children?
@@ -48,7 +47,7 @@
48
47
  = ' ' + I18n.t('nav.generated.on_date') + ' ' + Time.now.strftime(I18n.t('date_format'))
49
48
  = ' ' + I18n.t('date_time_separator') + Time.now.strftime(I18n.t('time_format'))
50
49
  %br
51
- %a{onclick: 'openModal();'}= I18n.t('nav.about')
50
+ %a{id: 'openmodal'}= I18n.t('nav.about')
52
51
  %aside#modal
53
52
  %div#modal-content
54
53
  %h2= 'Intranet'
@@ -4,23 +4,13 @@
4
4
  * and for the modal box.
5
5
  */
6
6
 
7
+ const openMenu = document.getElementById('openmenu');
8
+ const closeMenu = document.getElementById('closemenu');
9
+ const navMenu = document.querySelectorAll('header nav')[0];
10
+ openMenu.addEventListener('click', function() { navMenu.style.width = 'auto'; });
11
+ closeMenu.addEventListener('click', function() { navMenu.style.width = ''; });
7
12
 
8
- function openNavMenu() {
9
- document.querySelectorAll('header nav')[0].style.width = 'auto';
10
- }
11
-
12
- function closeNavMenu() {
13
- /* Remove value property set in openNavMenu() */
14
- document.querySelectorAll('header nav')[0].style.width = '';
15
- }
16
-
17
- function openModal() {
18
- document.getElementById('modal').style.display = 'block';
19
- }
20
-
21
- window.onclick = function(event) {
22
- if (event.target == document.getElementById('modal')) {
23
- document.getElementById('modal').style.display = 'none';
24
- }
25
- }
26
-
13
+ const openModal = document.getElementById('openmodal');
14
+ const modal = document.getElementById('modal');
15
+ openModal.addEventListener('click', function() { modal.style.display = 'block'; });
16
+ modal.addEventListener('click', function() { modal.style.display = 'none'; });
@@ -24,12 +24,11 @@ body {
24
24
  background: #1e262b;
25
25
  font-family: "Source Sans Pro", Cantarell, sans-serif;
26
26
  color: black;
27
- }
28
- /* Mobile devices only */
29
- @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
30
- body {
31
- font-size: 180%;
32
- }
27
+ font-size: 1.0rem;
28
+ -moz-text-size-adjust: none;
29
+ -webkit-text-size-adjust: none;
30
+ -ms-text-size-adjust: none;
31
+ font-size-adjust: none;
33
32
  }
34
33
 
35
34
  a {
@@ -51,7 +50,7 @@ header {
51
50
  }
52
51
  header h1 {
53
52
  float: left;
54
- font-size: 225%;
53
+ font-size: 2.25em;
55
54
  font-weight: 500;
56
55
  margin: 0px;
57
56
  padding: 5px 20px 8px; /* top sides bottom */
@@ -67,11 +66,11 @@ header nav ul {
67
66
  header nav > ul > li {
68
67
  display: inline-block;
69
68
  }
70
- header nav > ul > li a, header a#openmenu, header a#closemenu {
69
+ header nav > ul > li a {
71
70
  display: block;
72
71
  padding: 16px 20px;
73
72
  margin: 0px;
74
- font-size: 125%;
73
+ font-size: 1.25em;
75
74
  color: rgba(255, 255, 255, 0.7);
76
75
  }
77
76
  header nav > ul > li a:hover {
@@ -100,21 +99,18 @@ header a#openmenu, header a#closemenu {
100
99
 
101
100
  /* Mobile devices only */
102
101
  @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
103
- header {
104
- height: 100px;
105
- }
106
102
  header a#openmenu {
107
103
  display: block;
108
104
  position: absolute;
109
- top: 0;
110
- left: 0;
111
- font-size: 190%;
105
+ top: 12%;
106
+ left: 20px;
107
+ font-size: 2em;
112
108
  font-weight: bold;
113
109
  color: white;
114
110
  }
115
111
  header nav a#closemenu {
116
112
  display: block;
117
- font-size: 250%;
113
+ font-size: 2.5em;
118
114
  font-weight: bold;
119
115
  color: white;
120
116
  text-align: right;
@@ -132,21 +128,18 @@ header a#openmenu, header a#closemenu {
132
128
  padding: 0;
133
129
  z-index: 1;
134
130
  background: inherit;
135
- font-size: 125%;
131
+ font-size: 1.25em;
136
132
  overflow-y: scroll;
137
133
  width: 0;
138
134
  max-width: 100%;
139
135
  }
140
136
  header nav > ul {
141
137
  height: auto;
142
- margin: 0px 0px 50px; /* top sides bottom */
143
138
  }
144
139
  header nav > ul > li {
145
140
  display: block;
146
141
  }
147
142
  header nav > ul > li a {
148
- color: rgba(255, 255, 255, 1.0);
149
- margin-top: 35px;
150
143
  padding: 10px 150px 10px 40px; /* top right bottom left */
151
144
  }
152
145
  header nav > ul > li > ul {
@@ -154,21 +147,16 @@ header a#openmenu, header a#closemenu {
154
147
  position: static; /* reset absolute positionning */
155
148
  }
156
149
  header nav > ul > li > ul > li a {
157
- color: rgba(255, 255, 255, 0.7);
158
150
  margin: 0px;
159
151
  padding: 10px 100px 10px 90px; /* top right bottom left */
160
152
  }
161
- header nav li a:hover {
162
- color: inherit;
163
- background-color: #c2c2c2;
164
- }
165
153
  }
166
154
 
167
- /* breadcrump navigation menu */
155
+ /* breadcrumb navigation menu */
168
156
  ul.breadcrumb {
169
157
  list-style: none;
170
158
  padding: 0px;
171
- font-size: 90%;
159
+ font-size: 0.9em;
172
160
  margin-bottom: 50px;
173
161
  }
174
162
  ul.breadcrumb li {
@@ -178,7 +166,7 @@ ul.breadcrumb li+li:before {
178
166
  padding: 4px;
179
167
  content: "/\00a0";
180
168
  }
181
- ul.breadcrumb li a, main article a {
169
+ main a {
182
170
  color: #748ea3;
183
171
  }
184
172
 
@@ -193,7 +181,7 @@ ul.breadcrumb li a, main article a {
193
181
 
194
182
  body > main {
195
183
  background: #f7f8fa;
196
- padding: 57px 0px; /* height of the navigation bar */
184
+ padding: 58px 0px; /* height of the navigation bar */
197
185
  }
198
186
 
199
187
  body > main section, body > main article, body > main hr, body > footer p {
@@ -211,16 +199,13 @@ body > main hr {
211
199
 
212
200
  /* Mobile devices only */
213
201
  @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
214
- body > main {
215
- padding: 100px 0px;
216
- }
217
- body > main section, main hr, footer p {
202
+ body > main section, body > main hr, body > footer p {
218
203
  width: 90%;
219
204
  }
220
205
  }
221
206
 
222
207
  body > main section h2 {
223
- font-size: 200%;
208
+ font-size: 2em;
224
209
  text-align: center;
225
210
  margin: 50px 0px 2em; /* top sides bottom */
226
211
  }
@@ -228,6 +213,7 @@ body > main article h2 {
228
213
  margin-bottom: 40px;
229
214
  }
230
215
  body > main section h3 {
216
+ font-size: 1.17em;
231
217
  margin: 3em 0px 1em; /* top sides bottom */
232
218
  }
233
219
 
@@ -250,7 +236,9 @@ body > footer {
250
236
  text-align: center;
251
237
  color: white;
252
238
  padding: 15px 0px; /* top sides */
253
- font-size: 10pt;
239
+ }
240
+ body > footer p {
241
+ font-size: 0.85em;
254
242
  }
255
243
  body > footer aside#modal { /* modal box container */
256
244
  display: none;
@@ -266,11 +254,12 @@ body > footer aside#modal { /* modal box container */
266
254
  }
267
255
  body > footer aside#modal #modal-content { /* modal box */
268
256
  background-color: #f7f8fa;
269
- margin: 15% auto;
257
+ margin: auto;
258
+ margin-top: 50vh;
259
+ transform: translateY(-50%);
270
260
  padding: 20px 25px 40px; /* top sides bottom */
271
261
  border: 1px solid #1e262b;
272
- width: 400px;
273
- font-size: 125%;
262
+ width: 425px;
274
263
  }
275
264
  body > footer aside#modal #modal-content dl {
276
265
  display: grid;
@@ -285,12 +274,6 @@ body > footer aside#modal #modal-content dl dd {
285
274
  text-align: left;
286
275
  margin-left: 0;
287
276
  }
288
- /* Mobile devices only */
289
- @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
290
- body > footer {
291
- font-size: 13pt;
292
- }
293
- }
294
277
 
295
278
 
296
279
  /******************************************* Error pages ******************************************/
@@ -308,4 +291,3 @@ section#error p img {
308
291
  section#error a {
309
292
  color: #748ea3;
310
293
  }
311
-
@@ -61,6 +61,31 @@ RSpec.describe Intranet::Core do
61
61
  thread&.join
62
62
  end
63
63
 
64
+ it 'should not advertise server version in HTTP response headers' do
65
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
66
+ thread = Thread.new { @intranet.start }
67
+ while @intranet.instance_variable_get(:@server).status != :Running
68
+ end
69
+
70
+ socket = TCPSocket.new('localhost', @intranet.port)
71
+ socket.puts("GET /design/favicon.ico HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
72
+ server_identification = ''
73
+ expect(socket.gets).to include('HTTP/1.1 200 OK') # pre-requisite
74
+ while (line = socket.gets.chomp) # look for Server response header
75
+ break if line.empty?
76
+
77
+ if line.start_with?('Server: ')
78
+ server_identification = line[8..-1]
79
+ break
80
+ end
81
+ end
82
+ expect(server_identification).to be_empty
83
+ socket.close
84
+
85
+ @intranet.stop
86
+ thread&.join
87
+ end
88
+
64
89
  context 'when no module is registered' do
65
90
  it 'should return HTTP error 404 when requested for /index.html' do
66
91
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
@@ -219,13 +244,21 @@ RSpec.describe Intranet::Core do
219
244
  it 'should be called to retrieve the body of the page' do
220
245
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
221
246
 
222
- responder = Intranet::TestResponder.new(
223
- {
224
- '/index.html' => [206, 'text/html', { content: 'PARTIAL_CONTENT', title: 'MyTitle' }]
225
- },
226
- ['/responder.css', 'nav.css'],
227
- ['module.js', '/js/interactive.js']
228
- )
247
+ responder = Intranet::TestResponder.new({
248
+ '/index.html' => [
249
+ 206,
250
+ 'text/html',
251
+ {
252
+ content: 'PARTIAL_CONTENT',
253
+ title: 'MyTitle',
254
+ stylesheets: ['/resp.css', 'nav.css'],
255
+ scripts: [
256
+ { src: 'module.js', type: 'module' },
257
+ { src: '/js/interactive.js', defer: 'defer' }
258
+ ]
259
+ }
260
+ ]
261
+ })
229
262
  @intranet.register_module(responder, ['r'], responder.resources_dir)
230
263
 
231
264
  thread = Thread.new { @intranet.start }
@@ -255,11 +288,11 @@ RSpec.describe Intranet::Core do
255
288
  expect(html).to match(%r{<footer>.*#{hostname}.*</footer>}m)
256
289
 
257
290
  # Returned HTML document: includes all CSS dependencies, relative or absolute path
258
- expect(html).to match(%r{<link href='/responder.css' rel='stylesheet' type='text/css'})
291
+ expect(html).to match(%r{<link href='/resp.css' rel='stylesheet' type='text/css'})
259
292
  expect(html).to match(%r{<link href='/r/nav.css' rel='stylesheet' type='text/css'})
260
293
 
261
294
  # Returned HTML document: includes all JS dependencies
262
- expect(html).to match(%r{<script defer='defer' src='/r/module.js'></script>})
295
+ expect(html).to match(%r{<script src='/r/module.js' type='module'></script>})
263
296
  expect(html).to match(%r{<script defer='defer' src='/js/interactive.js'></script>})
264
297
 
265
298
  # Returned HTML document: includes Intranet Core name, version and URL
@@ -281,7 +314,7 @@ RSpec.describe Intranet::Core do
281
314
  responder = Intranet::TestResponder.new(
282
315
  '/index.html' => [206, 'text/html', { content: 'PARTIAL_CONTENT', title: 'MyTitle' }]
283
316
  )
284
- other_responder = Intranet::TestResponder.new({}, [], [], true)
317
+ other_responder = Intranet::TestResponder.new({}, true)
285
318
  @intranet.register_module(responder, %w[r], responder.resources_dir)
286
319
  @intranet.register_module(responder, %w[dep_th1], responder.resources_dir)
287
320
  @intranet.register_module(responder, %w[depth2 res_p1], responder.resources_dir)
@@ -6,10 +6,8 @@ module Intranet
6
6
  class TestResponder < AbstractResponder
7
7
  attr_reader :finalized
8
8
 
9
- def initialize(responses = {}, extra_css = [], extra_js = [], hide_from_menu = false) # rubocop:disable Metrics/ParameterLists
9
+ def initialize(responses = {}, hide_from_menu = false)
10
10
  @responses = responses
11
- @extra_css = extra_css
12
- @extra_js = extra_js
13
11
  @finalized = false
14
12
  @hide_from_menu = hide_from_menu
15
13
  end
@@ -47,14 +45,6 @@ module Intranet
47
45
  super(path, query)
48
46
  end
49
47
 
50
- def css_dependencies
51
- super + @extra_css
52
- end
53
-
54
- def js_dependencies
55
- super + @extra_js
56
- end
57
-
58
48
  private
59
49
 
60
50
  def dump_with_encoding(path, query)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intranet-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.1
4
+ version: 2.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ebling Mis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-08 00:00:00.000000000 Z
11
+ date: 2021-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: haml
@@ -211,8 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
211
  - !ruby/object:Gem::Version
212
212
  version: '0'
213
213
  requirements: []
214
- rubyforge_project:
215
- rubygems_version: 2.7.6.2
214
+ rubygems_version: 3.2.5
216
215
  signing_key:
217
216
  specification_version: 4
218
217
  summary: Core component to build a custom intranet.