intranet-core 2.3.1 → 2.4.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 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.