intranet-core 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|