intranet-core 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.
- checksums.yaml +7 -0
- data/README.md +38 -0
- data/lib/core_extensions.rb +12 -0
- data/lib/core_extensions/string.rb +43 -0
- data/lib/core_extensions/tree.rb +84 -0
- data/lib/core_extensions/webrick/httpresponse.rb +22 -0
- data/lib/intranet/abstract_responder.rb +34 -0
- data/lib/intranet/core.rb +125 -0
- data/lib/intranet/core/builder.rb +98 -0
- data/lib/intranet/core/haml_wrapper.rb +60 -0
- data/lib/intranet/core/locales.rb +47 -0
- data/lib/intranet/core/servlet.rb +42 -0
- data/lib/intranet/core/version.rb +8 -0
- data/lib/intranet/logger.rb +38 -0
- data/lib/intranet/resources/haml/http_error.haml +27 -0
- data/lib/intranet/resources/haml/skeleton.haml +52 -0
- data/lib/intranet/resources/haml/title_and_breadcrumb.haml +8 -0
- data/lib/intranet/resources/locales/en.yml +46 -0
- data/lib/intranet/resources/locales/fr.yml +46 -0
- data/lib/intranet/resources/www/background.jpg +0 -0
- data/lib/intranet/resources/www/error.png +0 -0
- data/lib/intranet/resources/www/favicon.ico +0 -0
- data/lib/intranet/resources/www/fonts/SourceSansPro.woff2 +0 -0
- data/lib/intranet/resources/www/nav.js +25 -0
- data/lib/intranet/resources/www/style.css +306 -0
- data/spec/core_extensions/string_spec.rb +135 -0
- data/spec/core_extensions/tree_spec.rb +208 -0
- data/spec/core_extensions/webrick/httpresponse_spec.rb +43 -0
- data/spec/intranet/core/fr.yml +5 -0
- data/spec/intranet/core/haml_wrapper_spec.rb +70 -0
- data/spec/intranet/core/locales_spec.rb +74 -0
- data/spec/intranet/core_spec.rb +403 -0
- data/spec/intranet/logger_spec.rb +129 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/test_responder/responder.rb +42 -0
- data/spec/test_responder/www/style.css +5 -0
- metadata +218 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'haml'
|
4
|
+
|
5
|
+
module Intranet
|
6
|
+
class Core
|
7
|
+
# Wrapps the Haml engine from the Haml gem.
|
8
|
+
# This module has to be included in any class wanting to generate a HTML/XML
|
9
|
+
# from HAML templates.
|
10
|
+
module HamlWrapper
|
11
|
+
# @!visibility protected
|
12
|
+
# The default path to the HAML files.
|
13
|
+
DEFAULT_HAML_DIR = File.join(__dir__, '..', 'resources', 'haml')
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# @!visibility protected
|
17
|
+
# Haml load path (not to be modified directly)
|
18
|
+
attr_accessor :load_path
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!visibility protected
|
22
|
+
# Initializes HAML templates load path.
|
23
|
+
# @param default_haml_dir [String] The path to the directory containing default HAML
|
24
|
+
# templates.
|
25
|
+
def self.initialize(default_haml_dir = DEFAULT_HAML_DIR)
|
26
|
+
self.load_path = [default_haml_dir]
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!visibility protected
|
30
|
+
# Adds a HAML templates directory.
|
31
|
+
# @param dir [String] The path to the directory to add.
|
32
|
+
def self.add_path(dir)
|
33
|
+
HamlWrapper.load_path.unshift(dir) # prepend load_path
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generate the HTML/XML from a given HAML template.
|
37
|
+
# @param template [String] The name of the HAML template to use.
|
38
|
+
# @param content [Hash] The additional content required to generate the HTML/XML.
|
39
|
+
# @return [String] The HTML/XML content.
|
40
|
+
def to_markup(template, content = {})
|
41
|
+
fd = File.read(find_template(template))
|
42
|
+
Haml::Engine.new(fd, escape_html: false).render(self, content)
|
43
|
+
rescue Errno::ENOENT # template file does not exists
|
44
|
+
''
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Look for an HAML template in the registered paths.
|
50
|
+
# @return [String] The path to the template if it exists, an empty string otherwise.
|
51
|
+
def find_template(template)
|
52
|
+
HamlWrapper.load_path.each do |dir|
|
53
|
+
candidate = File.join(dir, template + '.haml')
|
54
|
+
return candidate if File.exist?(candidate)
|
55
|
+
end
|
56
|
+
''
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'i18n'
|
4
|
+
|
5
|
+
module Intranet
|
6
|
+
class Core
|
7
|
+
# @!visibility protected
|
8
|
+
# Wrapps the initialisation of the i18n internationalization gem.
|
9
|
+
module Locales
|
10
|
+
# The default path to the locales files.
|
11
|
+
DEFAULT_LOCALES_DIR = File.join(__dir__, '..', 'resources', 'locales')
|
12
|
+
|
13
|
+
# Initializes translation units.
|
14
|
+
# @param default_locales_dir [String] The path to the directory containing default translation
|
15
|
+
# units.
|
16
|
+
def self.initialize(default_locales_dir = DEFAULT_LOCALES_DIR)
|
17
|
+
I18n.load_path = translation_files(default_locales_dir)
|
18
|
+
I18n.available_locales = available_locales(default_locales_dir)
|
19
|
+
begin
|
20
|
+
I18n.default_locale = ENV['LANG'].split('.')[0].split('_')[0].to_sym
|
21
|
+
rescue NoMethodError, I18n::InvalidLocale
|
22
|
+
I18n.default_locale = :en
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Adds a translation units directory.
|
27
|
+
# @param dir [String] The path to the directory to add.
|
28
|
+
def self.add_path(dir)
|
29
|
+
I18n.load_path = Dir.glob(File.join(dir, '*.yml')) + I18n.load_path # prepend load_path
|
30
|
+
end
|
31
|
+
|
32
|
+
# private
|
33
|
+
|
34
|
+
def self.translation_files(dir)
|
35
|
+
Dir.glob(File.join(dir, '*.yml'))
|
36
|
+
end
|
37
|
+
private_class_method :translation_files
|
38
|
+
|
39
|
+
def self.available_locales(dir)
|
40
|
+
translation_files(dir).map do |file|
|
41
|
+
File.basename(file, File.extname(file)).to_sym
|
42
|
+
end
|
43
|
+
end
|
44
|
+
private_class_method :available_locales
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'webrick'
|
5
|
+
require 'webrick/httpstatus'
|
6
|
+
|
7
|
+
module Intranet
|
8
|
+
class Core
|
9
|
+
# @!visibility protected
|
10
|
+
# WEBrick servlet for the Intranet.
|
11
|
+
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
12
|
+
# Initializes a new Servlet instance.
|
13
|
+
# @param server [WEBrick::HTTPServer] The web server.
|
14
|
+
# @param builder [Intranet::Core::Builder] The web pages builder.
|
15
|
+
def initialize(server, builder)
|
16
|
+
super(server)
|
17
|
+
@builder = builder
|
18
|
+
end
|
19
|
+
|
20
|
+
# Processes a GET request issued to the web server.
|
21
|
+
# @param request [WEBrick::HTTPRequest] The GET request.
|
22
|
+
# @param response [WEBrick::HTTPResponse] Fields +status+, +content_type+ and +body+ are
|
23
|
+
# filled with the server response.
|
24
|
+
def do_GET(request, response) # rubocop:disable Naming/MethodName
|
25
|
+
# See https://github.com/nahi/webrick/blob/master/lib/webrick/httprequest.rb
|
26
|
+
# We could use request.request_uri (which is a URI object) but its path is not normalized
|
27
|
+
# (it may contain '../' or event start with '..'). Hence we use request.past which has been
|
28
|
+
# normalized with HTTPUtils::normalize_path, and request.query for the URL parameters.
|
29
|
+
path = request.path
|
30
|
+
path += 'index.html' if path[-1] == '/' # path is normalized (ie. does not contain '../')
|
31
|
+
|
32
|
+
status, content_type, body = @builder.do_get(path, request.query)
|
33
|
+
|
34
|
+
raise WEBrick::HTTPStatus[status] if WEBrick::HTTPStatus.error?(status)
|
35
|
+
|
36
|
+
response.status = status
|
37
|
+
response.content_type = content_type
|
38
|
+
response.body = body
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'webrick'
|
4
|
+
|
5
|
+
module Intranet
|
6
|
+
# The default logger for the Intranet. It is based on +WEBrick::BasicLog+ but adds timestamp and
|
7
|
+
# colors to the messages.
|
8
|
+
class Logger < WEBrick::BasicLog
|
9
|
+
COLOR = {
|
10
|
+
FATAL => "\033[1;37;41m",
|
11
|
+
ERROR => "\033[0;31m",
|
12
|
+
WARN => "\033[0;33m",
|
13
|
+
INFO => "\033[0;36m",
|
14
|
+
DEBUG => "\033[0;35m"
|
15
|
+
}.freeze
|
16
|
+
private_constant :COLOR
|
17
|
+
|
18
|
+
# Initializes a new logger for $stderr that outputs messages at +level+ or higher.
|
19
|
+
# +level+ can be modified later (attribute accessor).
|
20
|
+
# @param level The initial log level.
|
21
|
+
def initialize(level = INFO)
|
22
|
+
super($stderr, level)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Logs a message at a given level if it is above the current log level.
|
26
|
+
# @param level The level of the message
|
27
|
+
# @param msg [String] The message
|
28
|
+
def log(level, msg)
|
29
|
+
super(level, COLOR[level] + Time.now.strftime('[%Y-%m-%d %H:%M:%S] ') + msg + "\033[0m")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Logs an object that responds to +to_s+ at level INFO.
|
33
|
+
# @param obj [Object] The object to log.
|
34
|
+
def <<(obj)
|
35
|
+
info(obj.to_s.chomp)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
!!! 5
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title
|
5
|
+
= HTMLEntities.new.encode(I18n.t('nav.error.http'), :named) + ' ' + error.to_s + ' | '
|
6
|
+
= HTMLEntities.new.encode(I18n.t('nav.error.http_' + error.to_s + '.short'), :named)
|
7
|
+
%meta{charset: 'utf-8'}
|
8
|
+
%link{rel: 'icon', type: 'image/x-icon', href: '/design/favicon.ico'}
|
9
|
+
%link{rel: 'stylesheet', type: 'text/css', href: '/design/style.css'}
|
10
|
+
%body
|
11
|
+
%header
|
12
|
+
%h1
|
13
|
+
%a{href: '/index.html'}= Socket.gethostname.capitalize
|
14
|
+
%main
|
15
|
+
%section#error
|
16
|
+
%h2
|
17
|
+
= HTMLEntities.new.encode(I18n.t('nav.error.http'), :named) + ' ' + error.to_s
|
18
|
+
%br
|
19
|
+
= HTMLEntities.new.encode(I18n.t('nav.error.http_' + error.to_s + '.short'), :named)
|
20
|
+
%p
|
21
|
+
%img{src: '/design/error.png', alt: ''}
|
22
|
+
%br
|
23
|
+
= HTMLEntities.new.encode(I18n.t('nav.error.http_' + error.to_s + '.full'), :named)
|
24
|
+
%br
|
25
|
+
%br
|
26
|
+
%a{href: '/index.html'}= HTMLEntities.new.encode(I18n.t('nav.back.home'), :named)
|
27
|
+
%footer
|
@@ -0,0 +1,52 @@
|
|
1
|
+
!!! 5
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title= Socket.gethostname.capitalize + ' | ' + body[:title]
|
5
|
+
%meta{charset: 'utf-8'}
|
6
|
+
%link{rel: 'icon', type: 'image/x-icon', href: '/design/favicon.ico'}
|
7
|
+
- css.each do |url|
|
8
|
+
%link{rel: 'stylesheet', type: 'text/css', href: url}
|
9
|
+
- js.each do |url|
|
10
|
+
%script{src: url, defer: 'defer'}
|
11
|
+
%body
|
12
|
+
%header
|
13
|
+
%a{id: 'openmenu', onclick: 'openNavMenu();'}= '☰'
|
14
|
+
%h1
|
15
|
+
%a{href: '/index.html', title: I18n.t('nav.back.home')}= Socket.gethostname.capitalize
|
16
|
+
%nav
|
17
|
+
%a{id: 'closemenu', onclick: 'closeNavMenu();'}= '×'
|
18
|
+
%ul
|
19
|
+
- responders.children_nodes.each do |child_id, child_node|
|
20
|
+
%li
|
21
|
+
- if child_node.children? # Create a dropdown menu entry
|
22
|
+
%a= I18n.t(child_id + '.menu')
|
23
|
+
%ul
|
24
|
+
- child_node.children_nodes.each do |child_url, node|
|
25
|
+
%li
|
26
|
+
%a{href: '/' + child_id + '/' + child_url + '/index.html'}
|
27
|
+
= I18n.t(child_id + '.' + child_url + '.menu')
|
28
|
+
- else # Create a regular menu entry
|
29
|
+
%a{href: '/' + child_id + '/index.html'}= I18n.t(child_id + '.menu')
|
30
|
+
|
31
|
+
- if is_home
|
32
|
+
%aside
|
33
|
+
|
34
|
+
%main
|
35
|
+
= body[:content]
|
36
|
+
|
37
|
+
%footer
|
38
|
+
%p
|
39
|
+
= I18n.t('nav.generated.page') + ' ' + I18n.t('nav.generated.on_host') + ' '
|
40
|
+
%em= Socket.gethostname
|
41
|
+
= ' ' + I18n.t('nav.generated.on_date') + ' ' + Time.now.strftime(I18n.t('date_format'))
|
42
|
+
= ' ' + I18n.t('date_time_separator') + Time.now.strftime(I18n.t('time_format'))
|
43
|
+
%br
|
44
|
+
%a{onclick: 'openModal();'}= I18n.t('nav.about')
|
45
|
+
%aside#modal
|
46
|
+
%div#modal-content
|
47
|
+
%h2= 'Intranet'
|
48
|
+
%h3
|
49
|
+
= 'core' + ' '
|
50
|
+
%em= Intranet::Core::VERSION
|
51
|
+
%p
|
52
|
+
%a{href: 'https://bitbucket.org/linuxtools/intranet.git', target: '_blank'}= 'https://bitbucket.org/linuxtools/intranet.git'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# en.yml - Translation file for the IntraNet in en-GB/en-US
|
2
|
+
---
|
3
|
+
|
4
|
+
en:
|
5
|
+
# Date and time format, as inputs to strftime
|
6
|
+
date_format: '%Y-%m-%d'
|
7
|
+
time_format: '%H:%M'
|
8
|
+
date_time_separator: ''
|
9
|
+
|
10
|
+
# Translations for navigation elements
|
11
|
+
nav:
|
12
|
+
home: 'Home'
|
13
|
+
about: 'About'
|
14
|
+
back:
|
15
|
+
home: 'Back to the homepage'
|
16
|
+
generated:
|
17
|
+
page: 'Page generated'
|
18
|
+
on_host: 'on'
|
19
|
+
on_date: 'on'
|
20
|
+
error:
|
21
|
+
http: 'Error'
|
22
|
+
http_400: {short: 'Bad Request', full: 'The request could not be understood by the server.'}
|
23
|
+
http_401: {short: 'Unauthorized', full: 'The requested page requires user authentication.'}
|
24
|
+
http_402: {short: 'Payment Required', full: 'The requested page requires payment.'}
|
25
|
+
http_403: {short: 'Forbidden', full: 'Access to the requested page is forbidden.'}
|
26
|
+
http_404: {short: 'Not Found', full: 'The requested page cannot be found.'}
|
27
|
+
http_405: {short: 'Method Not Allowed', full: 'The method specified in the request is not allowed for this resource.'}
|
28
|
+
http_406: {short: 'Not Acceptable', full: 'The request could not be accepted by the server.'}
|
29
|
+
http_407: {short: 'Proxy Authentication Required', full: 'The requested page requires user authentication with the proxy.'}
|
30
|
+
http_408: {short: 'Request Timeout', full: 'The server did not receive the request in the expected delay.'}
|
31
|
+
http_409: {short: 'Conflict', full: 'The request could not be completed due to a conflict with the current state of the resource.'}
|
32
|
+
http_410: {short: 'Gone', full: 'The requested resource is no longer available at the server and no forwarding address is known.'}
|
33
|
+
http_411: {short: 'Length Required', full: 'The request could not be understood by the server.'}
|
34
|
+
http_412: {short: 'Precondition Failed', full: 'The request could not be completed by the server.'}
|
35
|
+
http_413: {short: 'Request Entity Too Large', full: 'The request could not be completed by the server.'}
|
36
|
+
http_414: {short: 'Request-URI Too Long', full: 'The request could not be completed by the server.'}
|
37
|
+
http_415: {short: 'Unsupported Media Type', full: 'The request could not be completed by the server.'}
|
38
|
+
http_416: {short: 'Requested Range Not Satisfiable', full: 'The request could not be completed by the server.'}
|
39
|
+
http_417: {short: 'Expectation Failed', full: 'The request could not be completed by the server.'}
|
40
|
+
http_500: {short: 'Internal Server Error', full: 'The server encountered an unexpected error and is unable to fulfill the request.'}
|
41
|
+
http_501: {short: 'Not Implemented', full: 'The server does not support the functionality required to fulfill the request.'}
|
42
|
+
http_502: {short: 'Bad Gateway', full: 'The server received an invalid response from the upstream server.'}
|
43
|
+
http_503: {short: 'Service Unavailable', full: 'The server is temporary unable to handle the request. Please try again later.'}
|
44
|
+
http_504: {short: 'Gateway Timeout', full: 'The server did not receive a response from the upstream server in the expected delay.'}
|
45
|
+
http_505: {short: 'HTTP Version Not Supported', full: 'The server does not support the HTTP protocol version that was used in the request message.'}
|
46
|
+
load: 'Load error!'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# fr.yml - Translation file for the IntraNet in fr-FR
|
2
|
+
---
|
3
|
+
|
4
|
+
fr:
|
5
|
+
# Date and time format, as inputs to strftime
|
6
|
+
date_format: '%d/%m/%Y'
|
7
|
+
time_format: '%Hh%M'
|
8
|
+
date_time_separator: 'à '
|
9
|
+
|
10
|
+
# Translations for navigation elements
|
11
|
+
nav:
|
12
|
+
home: 'Accueil'
|
13
|
+
about: 'À propos'
|
14
|
+
back:
|
15
|
+
home: 'Retour à l''accueil'
|
16
|
+
generated:
|
17
|
+
page: 'Page générée'
|
18
|
+
on_host: 'sur'
|
19
|
+
on_date: 'le'
|
20
|
+
error:
|
21
|
+
http: 'Erreur'
|
22
|
+
http_400: {short: 'Requête malformée', full: 'La syntaxe de la requête est erronée.'}
|
23
|
+
http_401: {short: 'Authentification requise', full: 'La page demandée requiert une authentification.'}
|
24
|
+
http_402: {short: 'Paiement requis', full: 'La page demandée requiert un paiement.'}
|
25
|
+
http_403: {short: 'Accès interdit', full: 'L''accès à la page demandée est interdit.'}
|
26
|
+
http_404: {short: 'Page non trouvée', full: 'La page demandée n''existe pas.'}
|
27
|
+
http_405: {short: 'Méthode non autorisée', full: 'La méthode de requête n''est pas autorisée.'}
|
28
|
+
http_406: {short: 'Page non disponible', full: 'La page demandée n''est pas disponible.'}
|
29
|
+
http_407: {short: 'Authentification avec le proxy requise', full: 'La page demandée requiert une authentification avec le proxy.'}
|
30
|
+
http_408: {short: 'Requête expirée', full: 'Le client n''a pas produit de requête dans le délai que le serveur était prêt à attendre.'}
|
31
|
+
http_409: {short: 'Conflit', full: 'La page demandée n''est pas disponible.'}
|
32
|
+
http_410: {short: 'Page déplacée', full: 'La page demandée n''est plus disponible et aucune adresse de redirection n''est connue.'}
|
33
|
+
http_411: {short: 'Requête malformée', full: 'La longueur de la requête n''a pas été précisée.'}
|
34
|
+
http_412: {short: 'Précondition non vérifiée', full: 'La requête n''a pas pu être traitée par le server.'}
|
35
|
+
http_413: {short: 'Requête trop grande', full: 'La requête n''a pas pu être traitée par le server.'}
|
36
|
+
http_414: {short: 'URI trop longue', full: 'La requête n''a pas pu être traitée par le server.'}
|
37
|
+
http_415: {short: 'Format de requête non supporté', full: 'La requête n''a pas pu être traitée par le server.'}
|
38
|
+
http_416: {short: 'Requête incorrecte', full: 'La requête n''a pas pu être traitée par le server.'}
|
39
|
+
http_417: {short: 'Requête incorrecte', full: 'La requête n''a pas pu être traitée par le server.'}
|
40
|
+
http_500: {short: 'Erreur interne du serveur', full: 'Le serveur a rencontré une erreur et est incapable de poursuivre le traitement de la requête.'}
|
41
|
+
http_501: {short: 'Fonction non supportée', full: 'La fonctionnalité requise n''est pas supportée par le serveur.'}
|
42
|
+
http_502: {short: 'Réponse invalide', full: 'Le serveur a reçu une réponse invalide depuis le serveur distant.'}
|
43
|
+
http_503: {short: 'Service indisponible', full: 'Le serveur est temporairement indisponible.'}
|
44
|
+
http_504: {short: 'Délai d''attente de la réponse dépassé', full: 'Le serveur distant n''a pas répondu dans le délai attendu.'}
|
45
|
+
http_505: {short: 'Version du protocole HTTP non supportée', full: 'Le serveur ne gère pas la version demandée du protocole HTTP.'}
|
46
|
+
load: 'Erreur de chargement !'
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,25 @@
|
|
1
|
+
/**
|
2
|
+
* nav.js
|
3
|
+
* JavaScript functions for the main navigation menu (used on small screens)
|
4
|
+
* and for the modal box.
|
5
|
+
*/
|
6
|
+
|
7
|
+
|
8
|
+
function openNavMenu() {
|
9
|
+
document.querySelectorAll('header nav')[0].style.width = 'auto';
|
10
|
+
}
|
11
|
+
|
12
|
+
function closeNavMenu() {
|
13
|
+
document.querySelectorAll('header nav')[0].style.width = '0';
|
14
|
+
}
|
15
|
+
|
16
|
+
function openModal() {
|
17
|
+
document.getElementById('modal').style.display = 'block';
|
18
|
+
}
|
19
|
+
|
20
|
+
window.onclick = function(event) {
|
21
|
+
if (event.target == document.getElementById('modal')) {
|
22
|
+
document.getElementById('modal').style.display = 'none';
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
@@ -0,0 +1,306 @@
|
|
1
|
+
/**
|
2
|
+
* style.css
|
3
|
+
* Design for the IntraNet core.
|
4
|
+
*/
|
5
|
+
|
6
|
+
/******************************* External Fonts *******************************/
|
7
|
+
|
8
|
+
@font-face {
|
9
|
+
font-family: "Source Sans Pro";
|
10
|
+
font-style: normal;
|
11
|
+
font-weight: 300;
|
12
|
+
src: url("fonts/SourceSansPro.woff2") format("woff2");
|
13
|
+
unicode-range: U+0-FF, U+131, U+152-153, U+2C6, U+2DA, U+2DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
14
|
+
}
|
15
|
+
|
16
|
+
/******************************* General design *******************************/
|
17
|
+
|
18
|
+
body, html {
|
19
|
+
padding: 0px;
|
20
|
+
margin: 0px;
|
21
|
+
height: 100%;
|
22
|
+
}
|
23
|
+
body {
|
24
|
+
background: #1e262b url('background.jpg') fixed top right no-repeat;
|
25
|
+
font-family: "Source Sans Pro", Cantarell, sans-serif;
|
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
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
a {
|
36
|
+
color: inherit;
|
37
|
+
text-decoration: inherit;
|
38
|
+
cursor: pointer;
|
39
|
+
}
|
40
|
+
|
41
|
+
/********************************* Navigation *********************************/
|
42
|
+
|
43
|
+
header {
|
44
|
+
position: fixed;
|
45
|
+
top: 0px;
|
46
|
+
width: 100%;
|
47
|
+
margin: auto;
|
48
|
+
height: 57px;
|
49
|
+
background-color: #1e262b;
|
50
|
+
color: white;
|
51
|
+
z-index: 2;
|
52
|
+
}
|
53
|
+
header h1 {
|
54
|
+
float: left;
|
55
|
+
font-size: 225%;
|
56
|
+
font-weight: 500;
|
57
|
+
margin: 0px;
|
58
|
+
padding: 5px 20px 8px; /* top sides bottom */
|
59
|
+
}
|
60
|
+
header nav {
|
61
|
+
float: right;
|
62
|
+
padding: 0px 20px;
|
63
|
+
}
|
64
|
+
header nav ul {
|
65
|
+
margin: 0px;
|
66
|
+
padding: 0px;
|
67
|
+
}
|
68
|
+
header nav > ul > li {
|
69
|
+
display: inline-block;
|
70
|
+
}
|
71
|
+
header nav > ul > li a, header a#openmenu, header a#closemenu {
|
72
|
+
display: block;
|
73
|
+
padding: 16px 20px;
|
74
|
+
margin: 0px;
|
75
|
+
font-size: 125%;
|
76
|
+
color: rgba(255, 255, 255, 0.7);
|
77
|
+
}
|
78
|
+
header nav > ul > li a:hover {
|
79
|
+
color: rgba(255, 255, 255, 1.0);
|
80
|
+
}
|
81
|
+
|
82
|
+
/* dropdown menus */
|
83
|
+
header nav > ul > li > ul {
|
84
|
+
display: none;
|
85
|
+
background-color: #1e262b;
|
86
|
+
position: absolute;
|
87
|
+
}
|
88
|
+
header nav > ul > li > ul > li {
|
89
|
+
display: block;
|
90
|
+
}
|
91
|
+
header nav > ul > li > ul > li a {
|
92
|
+
padding: 8px 20px;
|
93
|
+
}
|
94
|
+
header nav > ul > li:hover > ul, header nav > ul > li:active > ul {
|
95
|
+
display: block;
|
96
|
+
}
|
97
|
+
|
98
|
+
header a#openmenu, header a#closemenu {
|
99
|
+
display: none;
|
100
|
+
}
|
101
|
+
|
102
|
+
/* Mobile devices only */
|
103
|
+
@media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
|
104
|
+
header {
|
105
|
+
height: 100px;
|
106
|
+
}
|
107
|
+
header a#openmenu {
|
108
|
+
display: block;
|
109
|
+
position: absolute;
|
110
|
+
top: 0;
|
111
|
+
left: 0;
|
112
|
+
font-size: 190%;
|
113
|
+
font-weight: bold;
|
114
|
+
color: white;
|
115
|
+
}
|
116
|
+
header nav a#closemenu {
|
117
|
+
display: block;
|
118
|
+
font-size: 250%;
|
119
|
+
font-weight: bold;
|
120
|
+
color: white;
|
121
|
+
text-align: right;
|
122
|
+
margin-right: 0.5em;
|
123
|
+
}
|
124
|
+
header h1 {
|
125
|
+
float: none;
|
126
|
+
text-align: center;
|
127
|
+
}
|
128
|
+
header nav {
|
129
|
+
position: fixed;
|
130
|
+
top: 0;
|
131
|
+
left: 0;
|
132
|
+
height: 100%;
|
133
|
+
padding: 0;
|
134
|
+
z-index: 1;
|
135
|
+
background: inherit;
|
136
|
+
font-size: 125%;
|
137
|
+
overflow-y: scroll;
|
138
|
+
width: 0;
|
139
|
+
max-width: 100%;
|
140
|
+
}
|
141
|
+
header nav > ul {
|
142
|
+
height: auto;
|
143
|
+
margin: 0px 0px 50px; /* top sides bottom */
|
144
|
+
}
|
145
|
+
header nav > ul > li {
|
146
|
+
display: block;
|
147
|
+
}
|
148
|
+
header nav > ul > li a {
|
149
|
+
color: rgba(255, 255, 255, 1.0);
|
150
|
+
margin-top: 35px;
|
151
|
+
padding: 10px 150px 10px 40px; /* top right bottom left */
|
152
|
+
}
|
153
|
+
header nav > ul > li > ul {
|
154
|
+
display: block;
|
155
|
+
position: static; /* reset absolute positionning */
|
156
|
+
}
|
157
|
+
header nav > ul > li > ul > li a {
|
158
|
+
color: rgba(255, 255, 255, 0.7);
|
159
|
+
margin: 0px;
|
160
|
+
padding: 10px 100px 10px 90px; /* top right bottom left */
|
161
|
+
}
|
162
|
+
header nav li a:hover {
|
163
|
+
color: inherit;
|
164
|
+
background-color: #c2c2c2;
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
/* breadcrump navigation menu */
|
169
|
+
ul.breadcrumb {
|
170
|
+
list-style: none;
|
171
|
+
padding: 0px;
|
172
|
+
font-size: 90%;
|
173
|
+
margin-bottom: 50px;
|
174
|
+
}
|
175
|
+
ul.breadcrumb li {
|
176
|
+
display: inline;
|
177
|
+
}
|
178
|
+
ul.breadcrumb li+li:before {
|
179
|
+
padding: 4px;
|
180
|
+
content: "/\00a0";
|
181
|
+
}
|
182
|
+
ul.breadcrumb li a, main article a {
|
183
|
+
color: #748ea3;
|
184
|
+
}
|
185
|
+
|
186
|
+
/* Mobile devices only */
|
187
|
+
@media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
|
188
|
+
ul.breadcrumb {
|
189
|
+
display: none;
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
/******************************** Main content *******************************/
|
194
|
+
|
195
|
+
body > aside {
|
196
|
+
height: 45%;
|
197
|
+
min-height: 350px;
|
198
|
+
max-height: 500px; /* less than height of background.jpg */
|
199
|
+
}
|
200
|
+
|
201
|
+
body > main {
|
202
|
+
background: #f2f2f2;
|
203
|
+
padding: 57px 0px; /* height of the navigation bar */
|
204
|
+
min-height: 500px; /* lower or equal to height of background.jpg (with padding) */
|
205
|
+
}
|
206
|
+
|
207
|
+
body > main section, body > main article, body > main hr, body > footer p {
|
208
|
+
width: 75%;
|
209
|
+
max-width: 1200px;
|
210
|
+
margin: auto;
|
211
|
+
}
|
212
|
+
body > main hr {
|
213
|
+
height: 2px;
|
214
|
+
border: 0px;
|
215
|
+
background: #1e262b;
|
216
|
+
margin-top: 3em;
|
217
|
+
margin-bottom: 3em;
|
218
|
+
}
|
219
|
+
|
220
|
+
/* Mobile devices only */
|
221
|
+
@media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
|
222
|
+
body > main {
|
223
|
+
padding: 100px 0px;
|
224
|
+
}
|
225
|
+
body > main section, main hr, footer p {
|
226
|
+
width: 90%;
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
body > main section h2 {
|
231
|
+
font-size: 200%;
|
232
|
+
text-align: center;
|
233
|
+
margin: 50px 0px 2em; /* top sides bottom */
|
234
|
+
}
|
235
|
+
body > main article h2 {
|
236
|
+
margin-bottom: 40px;
|
237
|
+
}
|
238
|
+
body > main section h3 {
|
239
|
+
margin: 3em 0px 1em; /* top sides bottom */
|
240
|
+
}
|
241
|
+
|
242
|
+
body > main div.loading {
|
243
|
+
width: 100px;
|
244
|
+
height: 100px;
|
245
|
+
margin: auto;
|
246
|
+
border: 16px solid #eaeaea;
|
247
|
+
border-radius: 50%;
|
248
|
+
border-top: 16px solid #a0c5e2;
|
249
|
+
animation: spin 2s linear infinite;
|
250
|
+
}
|
251
|
+
@keyframes spin { /* animation definition */
|
252
|
+
0% { transform: rotate(0deg); }
|
253
|
+
100% { transform: rotate(360deg); }
|
254
|
+
}
|
255
|
+
|
256
|
+
body > footer {
|
257
|
+
background: #1e262b;
|
258
|
+
text-align: center;
|
259
|
+
color: white;
|
260
|
+
padding: 15px 0px; /* top sides */
|
261
|
+
font-size: 10pt;
|
262
|
+
}
|
263
|
+
body > footer aside#modal { /* modal box container */
|
264
|
+
display: none;
|
265
|
+
position: fixed;
|
266
|
+
z-index: 10;
|
267
|
+
left: 0;
|
268
|
+
top: 0;
|
269
|
+
width: 100%;
|
270
|
+
height: 100%;
|
271
|
+
overflow: auto;
|
272
|
+
color: black;
|
273
|
+
background-color: rgba(0, 0, 0, 0.8);
|
274
|
+
}
|
275
|
+
body > footer aside#modal #modal-content { /* modal box */
|
276
|
+
background-color: #f2f2f2;
|
277
|
+
margin: 15% auto;
|
278
|
+
padding: 20px 25px 40px; /* top sides bottom */
|
279
|
+
border: 1px solid #1e262b;
|
280
|
+
width: 400px;
|
281
|
+
font-size: 125%;
|
282
|
+
}
|
283
|
+
/* Mobile devices only */
|
284
|
+
@media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
|
285
|
+
body > footer {
|
286
|
+
font-size: 13pt;
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
|
291
|
+
/********************************* Error pages *********************************/
|
292
|
+
|
293
|
+
section#error h2 {
|
294
|
+
color: #ff3333;
|
295
|
+
}
|
296
|
+
section#error p {
|
297
|
+
text-align: center;
|
298
|
+
}
|
299
|
+
section#error p img {
|
300
|
+
width: 25%;
|
301
|
+
margin-bottom: 3em;
|
302
|
+
}
|
303
|
+
section#error a {
|
304
|
+
color: #748ea3;
|
305
|
+
}
|
306
|
+
|