Browse Source

Merge branch 'RailsMigration'

Iñigo Valentin 4 năm trước cách đây
mục cha
commit
7a7e900952
100 tập tin đã thay đổi với 1976 bổ sung176 xóa
  1. 10 0
      .gitattributes
  2. 39 33
      .gitignore
  3. 1 0
      .ruby-version
  4. 60 0
      Gemfile
  5. 235 0
      Gemfile.lock
  6. 6 0
      Rakefile
  7. 3 0
      app/assets/config/manifest.js
  8. 0 0
      app/assets/images/.keep
  9. 0 0
      app/assets/images/control/close.svg
  10. 0 0
      app/assets/images/control/slid_d.svg
  11. 0 0
      app/assets/images/control/slid_l.svg
  12. 0 0
      app/assets/images/control/slid_r.svg
  13. 0 0
      app/assets/images/control/slid_u.svg
  14. 0 0
      app/assets/images/icon/home.svg
  15. 0 0
      app/assets/images/icon/profile.svg
  16. 0 0
      app/assets/images/icon/project.svg
  17. 0 0
      app/assets/images/lang/en.svg
  18. 0 0
      app/assets/images/lang/es.svg
  19. 0 0
      app/assets/images/lang/eu.svg
  20. 0 0
      app/assets/images/license/CC-0.svg
  21. 0 0
      app/assets/images/license/CC-BY-NC-ND.svg
  22. 0 0
      app/assets/images/license/CC-BY-NC-SA.svg
  23. 0 0
      app/assets/images/license/CC-BY-NC.svg
  24. 0 0
      app/assets/images/license/CC-BY-ND.svg
  25. 0 0
      app/assets/images/license/CC-BY.svg
  26. 0 0
      app/assets/images/license/GPLV3.svg
  27. 0 0
      app/assets/images/logo/logo.svg
  28. 0 0
      app/assets/images/social/app_store.svg
  29. 0 0
      app/assets/images/social/email.svg
  30. 0 0
      app/assets/images/social/facebook.svg
  31. 0 0
      app/assets/images/social/github.svg
  32. 0 0
      app/assets/images/social/google_play.svg
  33. 0 0
      app/assets/images/social/google_plus.svg
  34. 0 0
      app/assets/images/social/linkedin.svg
  35. 0 0
      app/assets/images/social/telegram.svg
  36. 0 0
      app/assets/images/social/twitter.svg
  37. 0 0
      app/assets/images/social/www.svg
  38. 0 0
      app/assets/javascripts/help.js
  39. 103 0
      app/assets/javascripts/main.js
  40. 48 0
      app/assets/javascripts/project.js
  41. 14 0
      app/assets/stylesheets/application.css
  42. 0 0
      app/assets/stylesheets/error.css
  43. 23 1
      app/assets/stylesheets/help.css
  44. 44 9
      app/assets/stylesheets/main.css
  45. 28 13
      app/assets/stylesheets/profile.css
  46. 3 0
      app/assets/stylesheets/profiles.scss
  47. 70 0
      app/assets/stylesheets/project.css
  48. 72 0
      app/assets/stylesheets/projects.css
  49. 246 26
      app/assets/stylesheets/ui.css
  50. 4 0
      app/channels/application_cable/channel.rb
  51. 4 0
      app/channels/application_cable/connection.rb
  52. 32 0
      app/controllers/application_controller.rb
  53. 0 0
      app/controllers/concerns/.keep
  54. 53 0
      app/controllers/contact_controller.rb
  55. 5 0
      app/controllers/error_controller.rb
  56. 4 0
      app/controllers/help_controller.rb
  57. 5 0
      app/controllers/main_controller.rb
  58. 6 0
      app/controllers/profile_controller.rb
  59. 16 0
      app/controllers/projects_controller.rb
  60. 2 0
      app/helpers/application_helper.rb
  61. 2 0
      app/helpers/error_helper.rb
  62. 2 0
      app/helpers/help_helper.rb
  63. 2 0
      app/helpers/profile_helper.rb
  64. 2 0
      app/helpers/projects_helper.rb
  65. 6 0
      app/javascript/channels/consumer.js
  66. 5 0
      app/javascript/channels/index.js
  67. 13 0
      app/javascript/packs/application.js
  68. 7 0
      app/jobs/application_job.rb
  69. 11 0
      app/mailers/application_mailer.rb
  70. 3 0
      app/models/application_record.rb
  71. 0 0
      app/models/concerns/.keep
  72. 4 0
      app/models/license.rb
  73. 3 0
      app/models/mail_adress.rb
  74. 5 0
      app/models/message.rb
  75. 19 0
      app/models/project.rb
  76. 4 0
      app/models/project_url.rb
  77. 4 0
      app/models/resume.rb
  78. 6 0
      app/models/social_link.rb
  79. 5 0
      app/models/technology.rb
  80. 3 0
      app/models/text.rb
  81. 9 0
      app/models/url_target.rb
  82. 13 0
      app/models/user.rb
  83. 7 0
      app/models/user_social_link.rb
  84. 26 0
      app/views/application_mailer/message_email.html.erb
  85. 18 0
      app/views/contact/error.erb
  86. 5 0
      app/views/contact/success.erb
  87. 31 0
      app/views/error/error_404.html.erb
  88. 41 0
      app/views/help/index.html.erb
  89. 90 0
      app/views/layouts/_contact_form.html.erb
  90. 72 0
      app/views/layouts/_footer.html.erb
  91. 34 0
      app/views/layouts/_header.html.erb
  92. 22 0
      app/views/layouts/application.html.erb
  93. 18 0
      app/views/layouts/mailer.html.erb
  94. 1 0
      app/views/layouts/mailer.text.erb
  95. 85 0
      app/views/main/index.html.erb
  96. 64 0
      app/views/profile/index.html.erb
  97. 43 0
      app/views/projects/index.html.erb
  98. 155 0
      app/views/projects/show.html.erb
  99. 0 26
      application/Application.php
  100. 0 68
      application/Context.php

+ 10 - 0
.gitattributes

@@ -0,0 +1,10 @@
+# See https://git-scm.com/docs/gitattributes for more about git attribute files.
+
+# Mark the database schema as having been generated.
+db/schema.rb linguist-generated
+
+# Mark the yarn lockfile as having been generated.
+yarn.lock linguist-generated
+
+# Mark any vendored files as having been vendored.
+vendor/* linguist-vendored

+ 39 - 33
.gitignore

@@ -1,38 +1,44 @@
-#OS files
-.directory
-.DS_Store
-.DS_Store?
-._*
-.Trashes
-ehthumbs.db
-Thumbs.db
-*.swp
+# See https://help.github.com/articles/ignoring-files for more about ignoring files.
+#
+# If you find yourself ignoring temporary files generated by your text editor
+# or operating system, you probably want to add a global ignore instead:
+#   git config --global core.excludesfile '~/.gitignore_global'
 
-#IDE files
-.kateproject.d/
-.kateproject
-.project
-.settings
-.externalToolBuilders
+# Ignore bundler config.
+/.bundle
+
+# Ignore all logfiles and tempfiles.
+/log/*
+/tmp/*
+!/log/.keep
+!/tmp/.keep
 
-#Local server and deploy tools
-deploy
-server_*
-.php_pid
+# Ignore pidfiles, but keep the directory.
+/tmp/pids/*
+!/tmp/pids/
+!/tmp/pids/.keep
 
-#SQL custom data
-sql/DES/
-sql/PRE/
-sql/DBG/
+# Ignore uploaded files in development.
+/storage/*
+!/storage/.keep
+/custom_data
+/public/assets
+.byebug_history
 
-#Passwords file
-.htpasswd*
-application/config/auth.php_*
+# Ignore master key for decrypting credentials and more.
+/config/master.key
+
+/public/packs
+/public/packs-test
+/node_modules
+/yarn-error.log
+yarn-debug.log*
+.yarn-integrity
+.project
+.custom-data
 
-#Content images
-public/img/content/*.png
-public/img/content/*.svg
-public/img/content/*/*.png
-public/img/content/*/*.svg
-public/img/content/*/*/*.png
-public/img/content/*/*/*.svg
+# Credentials and keys
+/config/credentials/*.key
+/config/credentials/*.yml.enc
+/config/credentials.yml.enc
+/config/master.key

+ 1 - 0
.ruby-version

@@ -0,0 +1 @@
+ruby-2.7.4

+ 60 - 0
Gemfile

@@ -0,0 +1,60 @@
+source 'https://rubygems.org'
+git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
+ruby '2.7.4'
+
+# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
+gem 'rails', '~> 6.1.4', '>= 6.1.4.1'
+# Use mysql as the database for Active Record
+gem 'mysql2', '~> 0.5'
+# Use Puma as the app server
+gem 'puma', '~> 5.0'
+# Use SCSS for stylesheets
+gem 'sass-rails', '>= 6'
+# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
+gem 'webpacker', '~> 5.0'
+# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
+gem 'turbolinks', '~> 5'
+# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
+gem 'jbuilder', '~> 2.7'
+# Use Redis adapter to run Action Cable in production
+# gem 'redis', '~> 4.0'
+# Use Active Model has_secure_password
+# gem 'bcrypt', '~> 3.1.7'
+
+# Use Active Storage variant
+# gem 'image_processing', '~> 1.2'
+
+# Reduces boot times through caching; required in config/boot.rb
+gem 'bootsnap', '>= 1.4.4', require: false
+
+gem 'turbolinks_render'
+
+gem 'loofah', '>= 2.12.0'
+
+group :development, :test do
+  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
+  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+end
+
+group :development do
+  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
+  gem 'web-console', '>= 4.1.0'
+  # Display performance information such as SQL time and flame graphs for each request in your browser.
+  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
+  #gem 'rack-mini-profiler', '~> 2.0'
+  gem 'listen', '~> 3.3'
+  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
+  gem 'spring'
+end
+
+group :test do
+  # Adds support for Capybara system testing and selenium driver
+  gem 'capybara', '>= 3.26'
+  gem 'selenium-webdriver'
+  # Easy installation and use of web drivers to run system tests with browsers
+  gem 'webdrivers'
+end
+
+# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

+ 235 - 0
Gemfile.lock

@@ -0,0 +1,235 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    actioncable (6.1.4.1)
+      actionpack (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+      nio4r (~> 2.0)
+      websocket-driver (>= 0.6.1)
+    actionmailbox (6.1.4.1)
+      actionpack (= 6.1.4.1)
+      activejob (= 6.1.4.1)
+      activerecord (= 6.1.4.1)
+      activestorage (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+      mail (>= 2.7.1)
+    actionmailer (6.1.4.1)
+      actionpack (= 6.1.4.1)
+      actionview (= 6.1.4.1)
+      activejob (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+      mail (~> 2.5, >= 2.5.4)
+      rails-dom-testing (~> 2.0)
+    actionpack (6.1.4.1)
+      actionview (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+      rack (~> 2.0, >= 2.0.9)
+      rack-test (>= 0.6.3)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.0, >= 1.2.0)
+    actiontext (6.1.4.1)
+      actionpack (= 6.1.4.1)
+      activerecord (= 6.1.4.1)
+      activestorage (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+      nokogiri (>= 1.8.5)
+    actionview (6.1.4.1)
+      activesupport (= 6.1.4.1)
+      builder (~> 3.1)
+      erubi (~> 1.4)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.1, >= 1.2.0)
+    activejob (6.1.4.1)
+      activesupport (= 6.1.4.1)
+      globalid (>= 0.3.6)
+    activemodel (6.1.4.1)
+      activesupport (= 6.1.4.1)
+    activerecord (6.1.4.1)
+      activemodel (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+    activestorage (6.1.4.1)
+      actionpack (= 6.1.4.1)
+      activejob (= 6.1.4.1)
+      activerecord (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+      marcel (~> 1.0.0)
+      mini_mime (>= 1.1.0)
+    activesupport (6.1.4.1)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      i18n (>= 1.6, < 2)
+      minitest (>= 5.1)
+      tzinfo (~> 2.0)
+      zeitwerk (~> 2.3)
+    addressable (2.8.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    bindex (0.8.1)
+    bootsnap (1.9.3)
+      msgpack (~> 1.0)
+    builder (3.2.4)
+    byebug (11.1.3)
+    capybara (3.36.0)
+      addressable
+      matrix
+      mini_mime (>= 0.1.3)
+      nokogiri (~> 1.8)
+      rack (>= 1.6.0)
+      rack-test (>= 0.6.3)
+      regexp_parser (>= 1.5, < 3.0)
+      xpath (~> 3.2)
+    childprocess (4.1.0)
+    concurrent-ruby (1.1.9)
+    crass (1.0.6)
+    erubi (1.10.0)
+    ffi (1.15.4)
+    globalid (1.0.0)
+      activesupport (>= 5.0)
+    i18n (1.8.11)
+      concurrent-ruby (~> 1.0)
+    jbuilder (2.11.3)
+      activesupport (>= 5.0.0)
+    listen (3.7.0)
+      rb-fsevent (~> 0.10, >= 0.10.3)
+      rb-inotify (~> 0.9, >= 0.9.10)
+    loofah (2.12.0)
+      crass (~> 1.0.2)
+      nokogiri (>= 1.5.9)
+    mail (2.7.1)
+      mini_mime (>= 0.1.1)
+    marcel (1.0.2)
+    matrix (0.4.2)
+    method_source (1.0.0)
+    mini_mime (1.1.2)
+    mini_portile2 (2.6.1)
+    minitest (5.14.4)
+    msgpack (1.4.2)
+    mysql2 (0.5.3)
+    nio4r (2.5.8)
+    nokogiri (1.12.5)
+      mini_portile2 (~> 2.6.1)
+      racc (~> 1.4)
+    public_suffix (4.0.6)
+    puma (5.5.2)
+      nio4r (~> 2.0)
+    racc (1.6.0)
+    rack (2.2.3)
+    rack-proxy (0.7.0)
+      rack
+    rack-test (1.1.0)
+      rack (>= 1.0, < 3)
+    rails (6.1.4.1)
+      actioncable (= 6.1.4.1)
+      actionmailbox (= 6.1.4.1)
+      actionmailer (= 6.1.4.1)
+      actionpack (= 6.1.4.1)
+      actiontext (= 6.1.4.1)
+      actionview (= 6.1.4.1)
+      activejob (= 6.1.4.1)
+      activemodel (= 6.1.4.1)
+      activerecord (= 6.1.4.1)
+      activestorage (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+      bundler (>= 1.15.0)
+      railties (= 6.1.4.1)
+      sprockets-rails (>= 2.0.0)
+    rails-dom-testing (2.0.3)
+      activesupport (>= 4.2.0)
+      nokogiri (>= 1.6)
+    rails-html-sanitizer (1.4.2)
+      loofah (~> 2.3)
+    railties (6.1.4.1)
+      actionpack (= 6.1.4.1)
+      activesupport (= 6.1.4.1)
+      method_source
+      rake (>= 0.13)
+      thor (~> 1.0)
+    rake (13.0.6)
+    rb-fsevent (0.11.0)
+    rb-inotify (0.10.1)
+      ffi (~> 1.0)
+    regexp_parser (2.2.0)
+    rexml (3.2.5)
+    rubyzip (2.3.2)
+    sass-rails (6.0.0)
+      sassc-rails (~> 2.1, >= 2.1.1)
+    sassc (2.4.0)
+      ffi (~> 1.9)
+    sassc-rails (2.1.2)
+      railties (>= 4.0.0)
+      sassc (>= 2.0)
+      sprockets (> 3.0)
+      sprockets-rails
+      tilt
+    selenium-webdriver (4.1.0)
+      childprocess (>= 0.5, < 5.0)
+      rexml (~> 3.2, >= 3.2.5)
+      rubyzip (>= 1.2.2)
+    semantic_range (3.0.0)
+    spring (3.1.1)
+    sprockets (4.0.2)
+      concurrent-ruby (~> 1.0)
+      rack (> 1, < 3)
+    sprockets-rails (3.4.1)
+      actionpack (>= 5.2)
+      activesupport (>= 5.2)
+      sprockets (>= 3.0.0)
+    thor (1.1.0)
+    tilt (2.0.10)
+    turbolinks (5.2.1)
+      turbolinks-source (~> 5.2)
+    turbolinks-source (5.2.0)
+    turbolinks_render (0.9.21)
+      actionpack (>= 5.2.0)
+      activesupport (>= 5.2.0)
+      railties (>= 5.2.0)
+      turbolinks-source (~> 5.1)
+    tzinfo (2.0.4)
+      concurrent-ruby (~> 1.0)
+    web-console (4.2.0)
+      actionview (>= 6.0.0)
+      activemodel (>= 6.0.0)
+      bindex (>= 0.4.0)
+      railties (>= 6.0.0)
+    webdrivers (5.0.0)
+      nokogiri (~> 1.6)
+      rubyzip (>= 1.3.0)
+      selenium-webdriver (~> 4.0)
+    webpacker (5.4.3)
+      activesupport (>= 5.2)
+      rack-proxy (>= 0.6.1)
+      railties (>= 5.2)
+      semantic_range (>= 2.3.0)
+    websocket-driver (0.7.5)
+      websocket-extensions (>= 0.1.0)
+    websocket-extensions (0.1.5)
+    xpath (3.2.0)
+      nokogiri (~> 1.8)
+    zeitwerk (2.5.1)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  bootsnap (>= 1.4.4)
+  byebug
+  capybara (>= 3.26)
+  jbuilder (~> 2.7)
+  listen (~> 3.3)
+  loofah (>= 2.12.0)
+  mysql2 (~> 0.5)
+  puma (~> 5.0)
+  rails (~> 6.1.4, >= 6.1.4.1)
+  sass-rails (>= 6)
+  selenium-webdriver
+  spring
+  turbolinks (~> 5)
+  turbolinks_render
+  tzinfo-data
+  web-console (>= 4.1.0)
+  webdrivers
+  webpacker (~> 5.0)
+
+RUBY VERSION
+   ruby 2.7.4p191
+
+BUNDLED WITH
+   2.1.4

+ 6 - 0
Rakefile

@@ -0,0 +1,6 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require_relative "config/application"
+
+Rails.application.load_tasks

+ 3 - 0
app/assets/config/manifest.js

@@ -0,0 +1,3 @@
+//= link_tree ../images
+//= link_directory ../stylesheets .css
+//= link_directory ../javascripts .js

+ 0 - 0
application/resources/text/HELP_INFO_TEXT__EN → app/assets/images/.keep


+ 0 - 0
public/img/layout/control/close.svg → app/assets/images/control/close.svg


+ 0 - 0
public/img/layout/control/slid_d.svg → app/assets/images/control/slid_d.svg


+ 0 - 0
public/img/layout/control/slid_l.svg → app/assets/images/control/slid_l.svg


+ 0 - 0
public/img/layout/control/slid_r.svg → app/assets/images/control/slid_r.svg


+ 0 - 0
public/img/layout/control/slid_u.svg → app/assets/images/control/slid_u.svg


+ 0 - 0
public/img/layout/icon/home.svg → app/assets/images/icon/home.svg


+ 0 - 0
public/img/layout/icon/profile.svg → app/assets/images/icon/profile.svg


+ 0 - 0
public/img/layout/icon/project.svg → app/assets/images/icon/project.svg


+ 0 - 0
public/img/layout/lang/en.svg → app/assets/images/lang/en.svg


+ 0 - 0
public/img/layout/lang/es.svg → app/assets/images/lang/es.svg


+ 0 - 0
public/img/layout/lang/eu.svg → app/assets/images/lang/eu.svg


+ 0 - 0
public/img/layout/license/CC-0.svg → app/assets/images/license/CC-0.svg


+ 0 - 0
public/img/layout/license/CC-BY-NC-ND.svg → app/assets/images/license/CC-BY-NC-ND.svg


+ 0 - 0
public/img/layout/license/CC-BY-NC-SA.svg → app/assets/images/license/CC-BY-NC-SA.svg


+ 0 - 0
public/img/layout/license/CC-BY-NC.svg → app/assets/images/license/CC-BY-NC.svg


+ 0 - 0
public/img/layout/license/CC-BY-ND.svg → app/assets/images/license/CC-BY-ND.svg


+ 0 - 0
public/img/layout/license/CC-BY.svg → app/assets/images/license/CC-BY.svg


+ 0 - 0
public/img/layout/license/GPLV3.svg → app/assets/images/license/GPLV3.svg


+ 0 - 0
public/img/layout/logo/logo.svg → app/assets/images/logo/logo.svg


+ 0 - 0
public/img/layout/social/app_store.svg → app/assets/images/social/app_store.svg


+ 0 - 0
public/img/layout/social/email.svg → app/assets/images/social/email.svg


+ 0 - 0
public/img/layout/social/facebook.svg → app/assets/images/social/facebook.svg


+ 0 - 0
public/img/layout/social/github.svg → app/assets/images/social/github.svg


+ 0 - 0
public/img/layout/social/google_play.svg → app/assets/images/social/google_play.svg


+ 0 - 0
public/img/layout/social/google_plus.svg → app/assets/images/social/google_plus.svg


+ 0 - 0
public/img/layout/social/linkedin.svg → app/assets/images/social/linkedin.svg


+ 0 - 0
public/img/layout/social/telegram.svg → app/assets/images/social/telegram.svg


+ 0 - 0
public/img/layout/social/twitter.svg → app/assets/images/social/twitter.svg


+ 0 - 0
public/img/layout/social/www.svg → app/assets/images/social/www.svg


+ 0 - 0
public/js/help.js → app/assets/javascripts/help.js


+ 103 - 0
app/assets/javascripts/main.js

@@ -0,0 +1,103 @@
+function showContactForm(){
+    clearContactErrors();
+    document.getElementById('contact_menu').style.display = "block";
+    document.getElementById('contact_progress').style.display = "none";
+    document.getElementById('contact_confirmation').style.display = "none";
+    document.getElementById('contact_bg').style.opacity = "0.6";
+    document.getElementById('contact_bg').style.display = "block";
+    document.getElementById('contact').style.display = "block";
+}
+function closeContactForm(){
+    document.getElementById('contact_bg').style.opacity = "0.0";
+    document.getElementById('contact_bg').style.display = "none";
+    document.getElementById('contact').style.display = "none";
+}
+function clearContactErrors(){
+    document.getElementById('sender_name_error').style.opacity = "0";
+    document.getElementById('sender_email_error').style.opacity = "0";
+    document.getElementById('text_error').style.opacity = "0";
+}
+function contactSuccess(){
+    clearContactErrors();
+    document.getElementById('contact_sender_name').value = "";
+    document.getElementById('contact_sender_email').value = "";
+    document.getElementById('contact_text').value = "";
+    document.getElementById('contact_menu').style.display = "none";
+    document.getElementById('contact_progress').style.display = "none";
+    document.getElementById('contact_confirmation').style.display = "block";
+}
+function contactError(response){
+    document.getElementById('contact_menu').style.display = "block";
+    document.getElementById('contact_progress').style.display = "none";
+    document.getElementById('contact_confirmation').style.display = "none";
+    var errors = JSON.parse(response.replace(/\s/g, '')).response.errors
+    for (let i = 0; i < errors.length; i++) {
+        if (errors[i] === "INVALID_EMAIL"){
+            document.getElementById('sender_email_error').style.display = "inline";
+        }
+        if (errors[i] === "INVALID_TEXT"){
+            document.getElementById('text_error').style.display = "inline";
+        }
+    }
+    
+}
+function sendMessage(e) {
+    // Prevent form submit
+    if (e.preventDefault) e.preventDefault();
+
+    // Hide previously shown errors
+    clearContactErrors();
+
+    // Validate fields
+    var success = true;
+    var sender_name = document.getElementById('contact_sender_name').value;
+    var sender_email = document.getElementById('contact_sender_email').value;
+    var text = document.getElementById('contact_text').value;
+    
+    // Sender name is always OK
+    // Sender email has to look like an email
+    const res = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+    if (res.test(String(sender_email).toLowerCase()) === false){
+        success = false;
+        document.getElementById('sender_email_error').style.opacity = "1";
+    }
+    // Text needs to have something
+    if (text.length < 1){
+        success = false;
+        document.getElementById('text_error').style.opacity = "1";
+    }
+    if (success === false){
+        return false;
+    }
+    
+    // Send request
+    var url = "/contact/";
+    var xhr = new XMLHttpRequest();
+    xhr.open("POST", url, true);
+    //xhr.setRequestHeader("Content-Type", "application/json");
+    xhr.onreadystatechange = function () {
+        if (xhr.readyState === 4) {
+            if (xhr.status === 200){
+                contactSuccess();
+            }
+            else{
+                contactError(xhr.responseText)
+            }
+        }};
+    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+    datastring =
+      'authenticity_token=' + encodeURI(document.getElementById('authenticity_token').value) +
+      '&sender_name=' + encodeURI(sender_name) +
+      '&sender_email=' + encodeURI(sender_email) +
+      '&text=' + encodeURI(text)
+    document.getElementById('contact_menu').style.display = "none";
+    document.getElementById('contact_progress').style.display = "block";
+    document.getElementById('contact_confirmation').style.display = "none";
+    xhr.send(datastring);
+
+
+    // You must return false to prevent the default form behavior
+    return false;
+}
+

+ 48 - 0
app/assets/javascripts/project.js

@@ -0,0 +1,48 @@
+// Populate this array as teh pages are loaded
+var images = [];
+selectedPhoto = 0;
+
+var galleryOpen = false;
+function closeGallery(){
+    document.getElementById('gallery_bg').style.opacity = "0.0";
+    document.getElementById('gallery_bg').style.display = "none";
+    document.getElementById('gallery').style.display = "none";
+    galleryOpen = false;
+}
+
+function showImage(index){
+    if (galleryOpen === false){
+        openGallery();
+    }
+    document.getElementById('gallery_image').src = images[index];
+    selectedPhoto = index;
+    var previews = document.getElementsByClassName('gallery_preview');
+    for (i = 0; i < previews.length; i ++){
+        previews[i].classList.remove('gallery_preview_selected');
+    }
+    document.getElementById('gallery_preview_' + index).classList.add('gallery_preview_selected')
+}
+
+
+function openGallery(){
+    document.getElementById('gallery_bg').style.opacity = "0.6";
+    document.getElementById('gallery_bg').style.display = "block";
+    document.getElementById('gallery').style.display = "block";
+    galleryOpen = true;
+}
+
+function previousPhoto(){
+    newIndex = selectedPhoto - 1;
+    if (newIndex < 0){
+        newIndex = images.length - 1;
+    }
+    showImage(newIndex);
+}
+
+function nextPhoto(){
+    newIndex = selectedPhoto + 1;
+    if (newIndex >= images.length){
+        newIndex = 0;
+    }
+    showImage(newIndex);
+}

+ 14 - 0
app/assets/stylesheets/application.css

@@ -0,0 +1,14 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
+ * vendor/assets/stylesheets directory can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
+ *
+ *= require_self
+ */

+ 0 - 0
public/css/error.css → app/assets/stylesheets/error.css


+ 23 - 1
public/css/help.css → app/assets/stylesheets/help.css

@@ -6,14 +6,30 @@ section{
     border-bottom-left-radius: 0.5em;
     transition: all .5s ease-in-out;
 }
+@media screen and (max-width : 800px){
+    section{
+        max-height: 2em;
+        width: 90%;
+    }
+}
 section.selected{
-    max-height: 15em;
+    max-height: 30em;
     border-bottom-right-radius: 1.5em;
     border-bottom-left-radius: 1.5em;
 }
+@media screen and (max-width : 800px){
+    section.selected{
+        max-height: 50em;
+    }
+}
 section h3{
     margin: 0;
 }
+@media screen and (max-width : 800px){
+    section h3{
+        margin: -0.8em -1em 0em -1em;
+    }
+}
 section h3 img.slider{
     width: 1em;
     height: 1em;
@@ -21,6 +37,12 @@ section h3 img.slider{
     float: left;
     margin-left: -2em;
 }
+@media screen and (max-width : 800px){
+    section h3 img.slider{
+        margin-left: 0;
+        margin-top: 0.1em;
+    }
+}
 section.selected h3 img.slider{
     transform: rotate(90deg);
 }

+ 44 - 9
public/css/home.css → app/assets/stylesheets/main.css

@@ -12,15 +12,26 @@ section#profile{
     }
 }
 section#profile article img#profile_image{
-    display: inline-block;
-    vertical-align: top;
+    vertical-align: middle;
     width: 25%;
     height: 25%;
     border-radius: 50%;
     box-shadow: 0 0 0 0.1em #000000, 0 0 0 0.3em #66bf38, 0 0 0 0.4em #000000;
+    float: left;
+    margin-right: 1em;
+    margin-bottom: 1em;
+}
+section#profile article h4#tagline{
+    display: inline-block;
+    vertical-align: middle;
+}
+@media screen and (max-width : 800px){
+    section#profile article h4#tagline{
+        display: inline;
+        text-align: justify;
+    }
 }
 section#profile article div#profile_content{
-    width: 60%;
     padding: 0.5em 1.5em;
     display: inline-block;
     vertical-align: top;
@@ -30,6 +41,11 @@ section#profile article div.buttons{
     text-align: right;
     padding: 0.5em 2em 0 2em;
 }
+@media screen and (max-width : 800px){
+    section#profile article div.buttons{
+        padding: 0;
+    }
+}
 
 section#profile article div.buttons a.a_button img{
     filter: invert(1) brightness(1.4);
@@ -48,10 +64,15 @@ section#projects{
     section#projects{
         display: block;
         width: 90%;
-        margin: 2em auto auto auto;
+        margin: 2em auto 0 auto;
+    }
+}
+@media screen and (max-width : 800px){
+    section#projects article.project table td.icon{
+        vertical-align: top;
     }
 }
-section#projects article.project img.project_image{
+section#projects article.project table td.icon img.project_image{
     padding: 0.5em;
     width: 5em;
     height: 5em;
@@ -61,17 +82,31 @@ section#projects article.project img.project_image{
     border-radius: 1em;
 }
 @media screen and (max-width : 800px){
-    section#projects article.project img.project_image{
-        padding: 0.9em;
-        width: 10em;
-        height: 10em;
+    section#projects article.project table td.icon img.project_image{
+        padding: 0.2em;
+        width: 10vw;
+        height: 10vw;
     }
 }
 section#projects article.project td.project_details{
     vertical-align: top;
     padding-left: 1em;
 }
+@media screen and (max-width : 800px){
+    section#projects article.project td.project_details{
+        padding-left: 0.3em;
+    }
+    section#projects article.project td.project_details h4{
+        margin-bottom: 0.2em;
+    }
+}
 section#projects div.buttons{
     text-align: right;
     padding: 0.5em 2em 0 2em;
 }
+@media screen and (max-width : 800px){
+    section#projects div.buttons{
+        padding: 0;
+        margin: 0.4em 0.4em;
+    }
+}

+ 28 - 13
public/css/profile.css → app/assets/stylesheets/profile.css

@@ -7,7 +7,6 @@ section#info{
     display: inline-block;
     vertical-align: top;
     width: 30%;
-    margin: auto 2em auto auto;
 }
 @media screen and (max-width : 800px){
     section#info{
@@ -15,9 +14,6 @@ section#info{
         margin: auto;
         text-align: center;
     }
-    section#info h3{
-        text-align: left;
-    }
 }
 
 section#info img#profile_image{
@@ -37,6 +33,15 @@ section#info img#profile_image{
         max-height: 12em;
     }
 }
+section#info h5{
+    margin-top: 0;
+}
+section#info h5 span{
+    display: block;
+    text-align: center;
+    font-size: 110%;
+    padding: 0.5em;
+}
 section#info div#social{
     text-align: center;
     margin: auto;
@@ -45,7 +50,7 @@ section#info div#social{
     section#info div#social{
         display: inline-block;
         vertical-align: middle;
-        width: 40%;
+        width: 100%;
         margin: auto;
     }
 }
@@ -87,25 +92,35 @@ section#about p{
     margin: 2em 4em;
     color: #335333;
 }
-section#about div#cv_download{
+@media screen and (max-width : 800px){
+    section#about p{
+        font-style: initial;
+        text-indent: 0.7em;
+        color: initial;
+        margin: 0 0 1em 0;
+    }
+}
+section#about div#resume_download{
     text-align: center;
 }
-section#about div#cv_download a.a_button{
+section#about div#resume_download a.a_button{
     display: inline-block;
     vertical-align: top;
-    margin: auto 3em 1.5em auto;
+    margin: auto 1.5em 1.5em auto;
 }
-section#about div#cv_download details{
+section#about div#resume_download details{
     display: inline-block;
     vertical-align: top;
     font-style: italic;
     margin-top: 0.5em;
+    width: 15em;
 }
-section#about div#cv_download details summary{
+section#about div#resume_download details summary{
     color: #777777;
     font-size: 90%;
-}
-section#about div#cv_download details ul{
     text-align: left;
-    margin: 0.1em;
 }
+section#about div#resume_download details ul{
+    text-align: left;
+    margin: 0.1em 0.1em 0.1em 0.1em;
+}

+ 3 - 0
app/assets/stylesheets/profiles.scss

@@ -0,0 +1,3 @@
+// Place all the styles related to the Profile controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: https://sass-lang.com/

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 70 - 0
app/assets/stylesheets/project.css


+ 72 - 0
app/assets/stylesheets/projects.css

@@ -0,0 +1,72 @@
+/* Projects page */
+section{
+    width: 90%;
+    margin: auto;
+    text-align: center;
+}
+
+section article.project{
+    position: relative;
+    display: inline-block;
+    vertical-align: top;
+    width: 40%;
+    margin: 2em 3%;
+    padding: 1em;
+    text-align: left;
+    /*border-left: 0.1em solid #aacaaa;
+    border-right: 0.1em solid #aacaaa;
+    background: linear-gradient(to bottom, #e5f0e500 0%, #e5f0e5ff 20%, #e5f0e5ff 80%, #e5f0e500 100%);*/
+}
+@media screen and (max-width : 800px){
+    section article.project{
+        width: 96%;
+        margin: 0.3em auto;
+        padding: 0.3em;
+    }
+}
+
+section article.project table{
+    width: 100%;
+}
+section article.project table td.logo{
+    text-align: center;
+    vertical-align: top;
+    width: 9em;
+}
+section article.project table td.logo img.project_image{
+    padding: 0.5em;
+    width: 7em;
+    height: 7em;
+    background-color: #cccccc;
+    border: 0.1em solid #60ae29;
+    box-shadow: inset 0 0 0.5em #00220077;
+    border-radius: 1em;
+}
+@media screen and (max-width : 800px){
+    section article.project table td.logo{
+        width: 17vw;
+    }
+    section article.project table td.logo img.project_image{
+        padding: 0.3em;
+        width: 15vw;
+        height: 15vw;
+        border-radius: 0.5em;
+    }
+}
+section article.project table td.project_details{
+    padding-left: 1.5em;
+    vertical-align: top;
+}
+@media screen and (max-width : 800px){
+    section article.project table td.project_details{
+        padding-left: 0.5em;
+    }
+    section article.project table td.project_details h4{
+        margin-bottom: 0.2em;
+    }
+}
+
+section article.project table span.project_tagline{
+    font-size: 80%;
+    font-style: italic;
+}

+ 246 - 26
public/css/ui.css → app/assets/stylesheets/ui.css

@@ -7,6 +7,10 @@
     font-family: 'Latin'; 
     src:  url('/fonts/Latin-Modern-Mono-Light-Bold.otf');
 }
+@font-face {
+    font-family: 'Handwritten'; 
+    src:  url('/fonts/Handwritten.ttf'); 
+}
 
 /* Elements to be shown only in large screens. */
 .desktop{
@@ -40,13 +44,13 @@ body{
     background-color: #000000;
     font-size: 90%;
     min-height: 100vh;
-	flex-direction: column;
-	display: flex;
+    flex-direction: column;
+    display: flex;
 }
 @media screen and (max-width : 800px){
-	body{
-	    font-size: 100%;
-	}
+    body{
+        font-size: 100%;
+    }
 }
 
 /* Style for all links. */
@@ -83,7 +87,6 @@ a{
 img.slider{
     width: 1em;
     height: 1em;
-    vertial-align: middle;
     transition: all .3s ease-in-out;
 }
 
@@ -112,7 +115,7 @@ header div#logo{
     font-family: 'Latin';
 }
 header nav{
-	width: 100%;
+    width: 100%;
 }
 header nav a{
     font-size: 1.5em;
@@ -145,13 +148,19 @@ header nav a img{
     vertical-align: bottom;
 }
 @media screen and (max-width : 800px){
+    header div#logo{
+        /* Adjusted for the text 'inigoValentin' */
+        font-size: 12vw;
+    }
     header nav{
         overflow: hidden;
         white-space: nowrap;
+        width: 95%;
+        margin: auto;
     }
     header nav a{
         display: inline-block;
-        width: 32%;
+        width: 30%;
         margin: 0;
         padding: 0.5em 0;
         text-align: center;
@@ -159,10 +168,13 @@ header nav a img{
         text-decoration: none;
         border: 0.1em solid #aaaaaa;
         background-color: #222222;
+        font-size: 4vw;
     }
     header nav a img{
-        height: 1.1em;
+        height: 1em;
+        width: 1em;
         vertical-align: middle;
+        margin-right: 0.2em;
     }
 }
 
@@ -176,12 +188,19 @@ footer{
     margin-left: auto;
     margin-right: auto;
     margin-top: 3em;
-    margin-bottom: 2em;
+    margin-bottom: 3.5em;
     color: #dddddd;
     bottom: 0;
     left: 0;
     padding: 1em 2em 3em 2em;
 }
+@media screen and (max-width : 800px){
+    footer{
+        padding: 1em 0;
+        margin-top: 1em;
+        margin-bottom: 7em;
+    }
+}
 
 /* A table containing all the elements in the footer */
 footer table#footer_table{
@@ -215,6 +234,13 @@ footer table#footer_table td#footer_left span#footer_follow{
     display: block;
     margin: 0 0 1em 0;
 }
+@media screen and (max-width : 800px){
+    /* It already has the class 'desktop', which usually hiddes elements */
+    /* for small devices, but I'm overriding the display property */
+    footer table#footer_table td#footer_left span#footer_follow{
+        display: none;
+    }
+}
 
 /* Icons for social media */
 footer table#footer_table td#footer_left img.footer_social_icon{
@@ -232,6 +258,12 @@ footer table#footer_table td#footer_center{
     text-align: center;
 }
 
+@media screen and (max-width : 800px){
+    footer table#footer_table td#footer_center{
+        display: none;
+    }
+}
+
 /* Right area of the footer */
 footer table#footer_table td#footer_right{
     width: 33%;
@@ -259,13 +291,128 @@ footer table#footer_table td#footer_right a.lang img:hover{
 }
 @media screen and (max-width : 800px){
     footer table#footer_table td#footer_right a.lang img{
-        height: 1.5em;
-        margin: 0.2em;
+        height: 1.2em;
+        margin: 0.1em;
         border-radius: 0.1em;
         border: 0.1em solid #555555;
         box-shadow: 0 0 0 #dde;
     }
 }
+/* The contact form */
+div#contact_bg{
+    display: none;
+    background: #000000;
+    opacity: 0.6;
+    transition: all .4s ease-in-out;
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+section#contact{
+    display: none;
+    position: fixed;
+    top: 20%;
+    width: 60%;
+    margin: auto;
+    left: 20%;
+}
+@media screen and (max-width : 800px){
+    section#contact{
+        width: 90%;
+        top: 15%;
+        left: 5%;
+    }
+}
+section#contact h3 img#contact_close{
+    float:right;
+    width: 1em;
+    height: 1em;
+}
+section#contact div.contact_buttons{
+    text-align: center;
+}
+section#contact div.contact_buttons input{
+    margin: 1em;
+}
+@media screen and (max-width : 800px){
+    section#contact div.contact_buttons input{
+        margin: 0.3em;
+    }
+}
+section#contact div#contact_menu{
+    height: 18em;
+}
+section#contact div#contact_menu table{
+    width: 100%;
+}
+section#contact div#contact_menu table td.label{
+    width: 8em;
+    text-align: right;
+    padding-right: 1em;
+    vertical-align: top;
+}
+section#contact div#contact_menu table input[type=text]{
+    width: 18em;
+}
+@media screen and (max-width : 800px){
+    section#contact div#contact_menu table td.label{
+        width: initial;
+        padding-right: 0.5em;
+        padding-bottom: 1em;
+    }
+    section#contact div#contact_menu table input[type=text]{
+        width: 90%;
+    }
+}
+section#contact div#contact_menu table textarea{
+    width: 90%;
+    height: 5em;
+    margin: auto;
+}
+section#contact div#contact_menu span.contact_error {
+    font-weight: bold;
+    color: #581010;
+    background-color: #E2AAAA;
+    border: 0.2em solid #E02323;
+    border-radius: 0.429em;
+    padding: 0.2em 0.5em;
+    text-shadow: 0 0 0.15em #0003;
+    cursor: pointer;
+    font-size: 80%;
+    margin-top: -0.5em;
+    margin-left: 1em;
+    transition: all .2s ease-in-out;
+}
+section#contact div#contact_progress{
+    height: 18em;
+    text-align: center;
+}
+section#contact div#contact_progress img{
+    margin-top: 6em;
+    width: 3em;
+    height: 3em;
+    animation: contact_progress 4s infinite linear;
+}
+@keyframes contact_progress {
+    from {
+        transform: rotate(0deg);
+    }
+    to {
+        transform: rotate(359deg);
+    }
+}
+section#contact div#contact_confirmation{
+    height: 18em;
+    text-align: center;
+}
+section#contact div#contact_confirmation p{
+    padding-top: 2em;
+    padding-bottom: 2em;
+    font-size: 150%;
+    font-weight: bold;
+}
 main{
     background-color: #000000;
     width: 100%;
@@ -297,6 +444,7 @@ section{
 @media screen and (max-width : 800px){
     section{
         margin: 1em;
+        padding: 0.2em;
     }
 }
 
@@ -317,6 +465,13 @@ section h3:before {
     opacity: 0.3;
 }
 
+@media screen and (max-width : 800px){
+    section h3{
+        font-size: 120%;
+        padding: 1em 2em 0.2em 1.5em;
+    }
+}
+
 /* .entry is a style only class to show content. Must be allways children os a .lighted element */
 section article{
     border: 0.1em solid #aaffaa00;
@@ -325,6 +480,13 @@ section article{
     margin: 0.5em;
     transition: all .4s ease-in-out;
 }
+@media screen and (max-width : 800px){
+    section article{
+        border-radius: 0.3em;
+        padding: 0.2em;
+        margin: 0.2em;
+    }
+}
 section article h4{
     font-variant: small-caps;
     text-decoration: underline;
@@ -332,6 +494,11 @@ section article h4{
     font-size: 140%;
     margin: 0.2em auto 1em auto;
 }
+@media screen and (max-width : 800px){
+    section article h4{
+        font-size: 120%;
+    }
+}
 
 section section_list:hover{
     background-color: #efffef;
@@ -416,7 +583,7 @@ input[type=submit]{
 }
 
 /* Style forr buttons */
-input[type=button]{
+input[type=button], input[type=submit]{
     cursor: pointer;
     display: inline-block;
     color: #cccccc;
@@ -435,10 +602,10 @@ input[type=button]{
     text-transform: capitalize;
 }
 @media screen and (max-width : 800px){
-	input[type=button]{
-		font-size: 120%;	
-		padding: 0.9em 1.4em;
-	}
+    input[type=button], input[type=submit]{
+        /*font-size: 120%;    
+        padding: 0.9em 1.4em;*/
+    }
 }
 input[type=button]::first-letter{
     color: #ffffff;
@@ -475,12 +642,11 @@ input[type=checkbox]{
 /* Style for textareas */
 textarea{
     width: 80%;
-    font-weight: bold;
-    color: #444444;
-    background-color: #bbbbbb;
-    border: 0.286em solid #dddddd; 
+    color: #224422;
+    background-color: #bbccbb;
+    border: 0.286em solid #ddeedd; 
     border-color: #dddddd;
-    border-radius: 0.286em;
+    border-radius: 0.429em;
     padding: 0.25em;
     text-shadow: 0 0 0.5em #ffffff;
     resize: none;
@@ -547,8 +713,8 @@ a.a_button{
 }
 @media screen and (max-width : 800px){
     a.a_button{
-        font-size: 120%;
-        padding: 0.9em 1.4em;
+        font-size: 100%;
+        padding: 0.3em 0.2em;
     }
 }
 a.a_button::first-letter{
@@ -601,7 +767,6 @@ img.comment_image{
     max-height: 6em;
     max-width: 60%;
     display: block;
-    vertical-align: top;
     border: 0.133em solid black;
     margin-left: 0.938em;
     margin-bottom: 0.938em;
@@ -610,7 +775,6 @@ img.comment_image{
 
 /* Paragraph with the comment text */
 p.comment_text{
-    vertical-align: top;
     width: auto;
     display: block;
     margin-top: 0.938em;
@@ -684,3 +848,59 @@ a.share img.share:hover{
     box-shadow: 0 0 0.7em #005500;
     border-radius: 0.7em;
 }
+
+span.technology{
+    display: inline-block;
+}
+
+span.technology img.technology_logo{
+    display: inline-block;
+    position:relative;
+    vertical-align: middle;
+    border-radius: 50%;
+    border-width: 0.2em;
+    z-index: 2;
+    background-color: #ffffff;
+    border-style: solid;
+    border-color: #000000;
+    height: 1.5em;
+    width: 1.5em;
+}
+span.technology span.technology_name{
+    display: inline-block;
+    position:relative;
+    vertical-align: middle;
+    border-radius: 15%;
+    border-width: 0.2em;
+    border-style: solid;
+    padding: 0.1em 0.2em 0.1em 1.1em;
+    z-index: 1;
+    background-color: #ffffff;
+    border-color: #000000;
+    margin-left: -1.3em;
+    font-size: 70%;
+    font-weight: bold;
+}
+
+span.technology_1 span.technology_name{
+    background-color: #f4a4a4;
+}
+span.technology_2 span.technology_name{
+    background-color: #ffc898;
+}
+span.technology_3 span.technology_name{
+    background-color: #a4c3f4;
+}
+span.technology_4 span.technology_name{
+    background-color: #f4a4e4;
+}
+span.technology_5 span.technology_name{
+    background-color: #a4f4a9;
+}
+span.technology_6 span.technology_name{
+    background-color: #f4e8a4;
+}
+
+
+
+

+ 4 - 0
app/channels/application_cable/channel.rb

@@ -0,0 +1,4 @@
+module ApplicationCable
+  class Channel < ActionCable::Channel::Base
+  end
+end

+ 4 - 0
app/channels/application_cable/connection.rb

@@ -0,0 +1,4 @@
+module ApplicationCable
+  class Connection < ActionCable::Connection::Base
+  end
+end

+ 32 - 0
app/controllers/application_controller.rb

@@ -0,0 +1,32 @@
+class ApplicationController < ActionController::Base
+    rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
+
+    before_action :set_locale
+    before_action :set_user
+    skip_before_action :verify_authenticity_token
+
+    private
+
+    def set_user
+        @user = User.find(1)
+    end
+    
+    def set_locale
+        I18n.locale = extract_locale || I18n.default_locale
+        Rails.application.reload_routes!
+    end
+
+    def extract_locale
+        parsed_locale = params[:locale]
+        I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
+    end
+    
+    def record_not_found(m, *args, &block)
+        Rails.logger.error("MMM" + m)
+        print("\nMETHOD MISSING\n")
+        redirect_to :controller=>"error", :action=>"error_404"
+        # or render/redirect_to somewhere else
+    end
+
+    
+end

+ 0 - 0
application/resources/text/HELP_INFO_TEXT__ES → app/controllers/concerns/.keep


+ 53 - 0
app/controllers/contact_controller.rb

@@ -0,0 +1,53 @@
+class ContactController < ApplicationController
+    def index
+        @message = Message.new
+    end
+    def create
+        # Validate message fields
+        valid = true
+        @errors = []
+        # Name is irrelevant, don't vlaidate
+        # Email has to LOOK like an email
+        if !(request.POST["sender_email"].match(URI::MailTo::EMAIL_REGEXP))
+             valid = false
+             @errors.push("INVALID_EMAIL")
+        end
+        # Text needs to have something
+        if request.POST["text"].length < 1
+            valid = false
+            @error.push("INVALID_TEXT")
+        end
+
+        # If valid, save and send the email
+        if valid
+            @message = Message.new()
+            @message.user_id = @user.id
+            @message.sender_name = request.POST["sender_name"]
+            @message.sender_email = request.POST["sender_email"]
+            @message.text = request.POST["text"]
+            @message.dtime = Time.now
+            @message.language = I18n.locale
+            @message.ip = request.remote_ip
+            
+        
+            if @message.save
+                ApplicationMailer.message_email(@user, @message).deliver_later
+            else
+                print("Error saving message: \n")
+                print("   FROM: " + request.POST["sender_name"] + "(" + request.POST["sender_email"] + ")\n")
+                print("   TEXT: " + request.POST["text"] + ")\n")
+                print(@message.errors.full_messages)
+                print("\n")
+            end
+            respond_to do |format|
+                format.json { render 'contact/success'}
+            end
+        # If invalid, return the error
+        else
+            respond_to do |format|
+                format.json { render 'contact/error', status: 400}
+            end
+        end
+    end
+
+end

+ 5 - 0
app/controllers/error_controller.rb

@@ -0,0 +1,5 @@
+class ErrorController < ApplicationController
+    def error_404
+        print("\nERRORCONTROLLER\n")
+    end
+end

+ 4 - 0
app/controllers/help_controller.rb

@@ -0,0 +1,4 @@
+class HelpController < ApplicationController
+    def index
+    end
+end

+ 5 - 0
app/controllers/main_controller.rb

@@ -0,0 +1,5 @@
+class MainController < ApplicationController
+    def index
+        @projects = Project.where(user_id: @user.id).order(priority: :desc).limit(3)
+    end
+end

+ 6 - 0
app/controllers/profile_controller.rb

@@ -0,0 +1,6 @@
+class ProfileController < ApplicationController
+    def index
+        @resume_main = @user.resumes.where(user_id: @user.id, language: I18n.locale).order(priority: :desc)[0]
+        @resume_languages = @user.resumes.where(user_id: @user.id, priority: @resume_main.priority).where.not(language: I18n.locale)
+    end
+end

+ 16 - 0
app/controllers/projects_controller.rb

@@ -0,0 +1,16 @@
+class ProjectsController < ApplicationController
+    def index
+        @projects = Project.where(user_id: @user.id).order(priority: :desc)
+    end
+    def show
+        @project = Project.where(permalink: params[:permalink])[0]
+        if @project == nil
+            print("ERROR 404")
+            #render status: 404
+            #redirect_to '/404'
+            respond_to do |format|
+                format.all { render "error/error_404", status: 404}
+              end
+        end
+    end
+end

+ 2 - 0
app/helpers/application_helper.rb

@@ -0,0 +1,2 @@
+module ApplicationHelper
+end

+ 2 - 0
app/helpers/error_helper.rb

@@ -0,0 +1,2 @@
+module ErrorHelper
+end

+ 2 - 0
app/helpers/help_helper.rb

@@ -0,0 +1,2 @@
+module HelpHelper
+end

+ 2 - 0
app/helpers/profile_helper.rb

@@ -0,0 +1,2 @@
+module ProfileHelper
+end

+ 2 - 0
app/helpers/projects_helper.rb

@@ -0,0 +1,2 @@
+module ProjectsHelper
+end

+ 6 - 0
app/javascript/channels/consumer.js

@@ -0,0 +1,6 @@
+// Action Cable provides the framework to deal with WebSockets in Rails.
+// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
+
+import { createConsumer } from "@rails/actioncable"
+
+export default createConsumer()

+ 5 - 0
app/javascript/channels/index.js

@@ -0,0 +1,5 @@
+// Load all the channels within this directory and all subdirectories.
+// Channel files must be named *_channel.js.
+
+const channels = require.context('.', true, /_channel\.js$/)
+channels.keys().forEach(channels)

+ 13 - 0
app/javascript/packs/application.js

@@ -0,0 +1,13 @@
+// This file is automatically compiled by Webpack, along with any other files
+// present in this directory. You're encouraged to place your actual application logic in
+// a relevant structure within app/javascript and only use these pack files to reference
+// that code so it'll be compiled.
+
+import Rails from "@rails/ujs"
+import Turbolinks from "turbolinks"
+import * as ActiveStorage from "@rails/activestorage"
+import "channels"
+
+Rails.start()
+Turbolinks.start()
+ActiveStorage.start()

+ 7 - 0
app/jobs/application_job.rb

@@ -0,0 +1,7 @@
+class ApplicationJob < ActiveJob::Base
+  # Automatically retry jobs that encountered a deadlock
+  # retry_on ActiveRecord::Deadlocked
+
+  # Most jobs are safe to ignore if the underlying records are no longer available
+  # discard_on ActiveJob::DeserializationError
+end

+ 11 - 0
app/mailers/application_mailer.rb

@@ -0,0 +1,11 @@
+class ApplicationMailer < ActionMailer::Base
+    layout 'mailer'
+    default from: Rails.application.credentials.smtp[:username]
+   
+    def message_email(user, message)
+        @message = message
+        to = user.mail_adresses[0].address
+        to = Rails.application.credentials.smtp[:username]
+        mail(to: to, subject: 'New message')
+    end
+end

+ 3 - 0
app/models/application_record.rb

@@ -0,0 +1,3 @@
+class ApplicationRecord < ActiveRecord::Base
+  self.abstract_class = true
+end

+ 0 - 0
application/resources/text/HELP_INFO_TEXT__EU → app/models/concerns/.keep


+ 4 - 0
app/models/license.rb

@@ -0,0 +1,4 @@
+class License < ApplicationRecord
+    has_many :project
+    has_one_attached :logo
+end

+ 3 - 0
app/models/mail_adress.rb

@@ -0,0 +1,3 @@
+class MailAdress < ApplicationRecord
+    belongs_to :user
+end

+ 5 - 0
app/models/message.rb

@@ -0,0 +1,5 @@
+class Message < ApplicationRecord
+    belongs_to :user
+    validates :sender_email, presence: true
+    validates :text, presence: true, length: { minimum: 10 }
+end

+ 19 - 0
app/models/project.rb

@@ -0,0 +1,19 @@
+class Project < ApplicationRecord
+    has_one_attached :logo
+    has_and_belongs_to_many :technologies
+    has_many :projectUrls
+    belongs_to :license
+    has_many_attached :images
+    def i18n_title
+        return Text.where(user_id: self.user_id, language: I18n.locale, key: self.title)[0].text
+    end
+    def i18n_header
+        return Text.where(user_id: self.user_id, language: I18n.locale, key: self.header)[0].text
+    end
+    def i18n_text
+        return Text.where(user_id: self.user_id, language: I18n.locale, key: self.text)[0].text
+    end
+    def i18n_comment
+        return Text.where(user_id: self.user_id, language: I18n.locale, key: self.comment)[0].text
+    end
+end

+ 4 - 0
app/models/project_url.rb

@@ -0,0 +1,4 @@
+class ProjectUrl < ApplicationRecord
+      belongs_to :project
+      belongs_to :urlTarget
+end

+ 4 - 0
app/models/resume.rb

@@ -0,0 +1,4 @@
+class Resume < ApplicationRecord
+    belongs_to :user
+    has_one_attached :file
+end

+ 6 - 0
app/models/social_link.rb

@@ -0,0 +1,6 @@
+class SocialLink < ApplicationRecord
+    has_one_attached :logo
+    def i18n_link_title
+        return Text.where(language: I18n.locale, key: self.link_title)[0].text
+    end
+end

+ 5 - 0
app/models/technology.rb

@@ -0,0 +1,5 @@
+class Technology < ApplicationRecord
+    has_one_attached :logo
+    has_and_belongs_to_many :users
+    has_and_belongs_to_many :projects
+end

+ 3 - 0
app/models/text.rb

@@ -0,0 +1,3 @@
+class Text < ApplicationRecord
+    belongs_to :user, optional: true
+end

+ 9 - 0
app/models/url_target.rb

@@ -0,0 +1,9 @@
+class UrlTarget < ApplicationRecord
+    has_one_attached :logo
+    def i18n_name
+        return Text.where(language: I18n.locale, key: self.name)[0].text
+    end
+    def i18n_summary
+        return Text.where(language: I18n.locale, key: self.summary)[0].text
+    end
+end

+ 13 - 0
app/models/user.rb

@@ -0,0 +1,13 @@
+class User < ApplicationRecord
+    has_many :mail_adresses
+    has_many :resumes
+    has_many_attached :picture
+    has_and_belongs_to_many :technologies
+    has_many :user_social_links
+    def i18n_tagline
+        return Text.where(user_id: self.id, language: I18n.locale, key: self.tagline)[0].text
+    end
+    def i18n_bio
+        return Text.where(user_id: self.id, language: I18n.locale, key: self.bio)[0].text
+    end
+end

+ 7 - 0
app/models/user_social_link.rb

@@ -0,0 +1,7 @@
+class UserSocialLink < ApplicationRecord
+    belongs_to :user
+    belongs_to :social_link
+    def generate_link
+        return self.social_link.pattern.sub("*", self.link)
+    end
+end

+ 26 - 0
app/views/application_mailer/message_email.html.erb

@@ -0,0 +1,26 @@
+<div>
+    New message recieved on the website:
+    <ul>
+        <li>
+            <span>
+                From:
+            </span>
+            <%= @message.sender_name %>
+        </li>
+        <li>
+            <span>
+                eMail:
+            </span>
+            <%= @message.sender_email %>
+        </li>
+        <li>
+            <span>
+                Time:
+            </span>
+            <%= @message.dtime %>
+        </li>
+    </ul>
+</div>
+<pre>
+    <%= @message.text %>
+</pre>

+ 18 - 0
app/views/contact/error.erb

@@ -0,0 +1,18 @@
+{
+    "response": {
+        "status": "ERROR",
+        "errors": [
+            <%
+                i = 0
+                max = @errors.length
+                @errors.each do |error|
+            %>
+                    "<%= error %>"
+                    <% if (i + 1) < max %>
+                        ,
+                    <% end %>
+                    <% i = i + 1 %>
+            <% end %>
+        ]
+    }
+}

+ 5 - 0
app/views/contact/success.erb

@@ -0,0 +1,5 @@
+{
+  "response": {
+    "status": "SUCCESS"
+  }
+}

+ 31 - 0
app/views/error/error_404.html.erb

@@ -0,0 +1,31 @@
+<% content_for :head do %>
+    <%= stylesheet_link_tag "error", media: "all", "data-turbolinks-track": "reload" %>
+<% end %>
+<section>
+    <h3>
+        <%= t("error.error_404_title" ) %>
+    </h3>
+    <div id='left'>
+        <p id='code'>
+            404
+        </p>
+        <p>
+            <span><%= t("error.error" ) %></span>
+            <span><%= t("error.code" ) %></span>
+        </p>
+    </div>
+    <div id='right'>
+        <h4>
+           <%= t("error.error_404_description" ) %>
+        </h4>
+        <%= t("error.try_solution" ) %>
+        <ul>
+            <li>
+                <a href='javascript: history.go(-1);'><%= t("error.error_400_solution_0") %></a>
+            </li>
+            <li>
+                <a href='/'><%= t("error.error_400_solution_1") %></a>
+            </li>
+        </ul>
+    </div>
+</section>

+ 41 - 0
app/views/help/index.html.erb

@@ -0,0 +1,41 @@
+<% content_for :head do %>
+    <%= stylesheet_link_tag "help", media: "all", "data-turbolinks-track": "reload" %>
+    <%= javascript_include_tag "help", "data-turbolinks-track" => true  %>
+<% end %>
+<section id='about' class='help'>
+    <h3 class='pointer' onClick='toggleHelp("about");'>
+        <%= image_tag("control/slid_r.svg", class: "slider", alt: t("help.expand")) %>
+        <%= t("help.about_title") %>
+    </h3>
+    <p>
+        <%= t("help.about_text").html_safe %>
+    </p>
+</section>
+<section id='privacy' class='help'>
+    <h3 class='pointer' onClick='toggleHelp("privacy");'>
+        <%= image_tag("control/slid_r.svg", class: "slider", alt: t("help.expand")) %>
+        <%= t("help.privacy_title") %>
+    </h3>
+    <p>
+        <%= t("help.privacy_text").html_safe %>
+    </p>
+</section>
+<section id='cookies' class='help'>
+    <h3 class='pointer' onClick='toggleHelp("cookies");'>
+        <%= image_tag("control/slid_r.svg", class: "slider", alt: t("help.expand")) %>
+        <%= t("help.cookies_title") %>
+    </h3>
+    <p>
+        <%= t("help.cookies_text").html_safe %>
+    </p>
+</section>
+<section id='license' class='help'>
+    <h3 class='pointer' onClick='toggleHelp("license");'>
+        <%= image_tag("control/slid_r.svg", class: "slider", alt: t("help.expand")) %>
+        <%= t("help.license_title") %>
+    </h3>
+    <p>
+        <%= t("help.license_text").html_safe %>
+    </p>
+</section>
+

+ 90 - 0
app/views/layouts/_contact_form.html.erb

@@ -0,0 +1,90 @@
+<div id='contact_bg'>
+</div>
+<section id='contact' >
+    <h3>
+        <%= t("contact.title") %>
+        <span id='contact_close'>
+            <%=
+                link_to(
+                    image_tag(
+                        "control/close.svg", 
+                        id: "contact_close",
+                        alt: t("contact.cancel")
+                    ),
+                    #"mailto:" + @user.mail_adresses[0].address,
+                    "javascript:closeContactForm()"
+                )
+            %>
+        </span>
+    </h3>
+    <div id='contact_menu'>
+        <form id='contact_form' action='/<%=I18n.locale%>/contact/' method='post'>
+            <%= hidden_field_tag :authenticity_token, form_authenticity_token %>
+            <table>
+                <tr>
+                    <td class='label'>
+                        <%= t("contact.name") %>
+                    </td>
+                    <td class='input'>
+                        <input type='text' name='sender_name' id='contact_sender_name'/>
+                        <br/>
+                        <span class='contact_error' id='sender_name_error'>
+                        </span>
+                    </td>
+                </tr>
+                <tr>
+                    <td class='label'>
+                        <%= t("contact.mail") %>
+                    </td>
+                    <td class='input'>
+                        <input type='text' name='sender_email' id='contact_sender_email'/>
+                        <br/>
+                        <span class='contact_error' id='sender_email_error'>
+                            <%= t('contact.error.invalid_email') %>
+                        </span>
+                    </td>
+                </tr>
+                <tr>
+                    <td class='label'>
+                        <%= t("contact.message") %>
+                    </td>
+                    <td class='input'>
+                        <textarea type='text' name='text' id='contact_text'></textarea>
+                        <br/>
+                        <span class='contact_error' id='text_error'>
+                            <%= t('contact.error.empty_text') %>
+                        </span>
+                    </td>
+                </tr>
+            </table>
+            <div class='contact_buttons'>
+                <input type='button' value='<%=t("contact.cancel")%>' onClick='closeContactForm();'>
+                <input type='submit' value='<%=t("contact.send")%>'>
+            </div>
+        </form>
+    </div>
+    <div id='contact_progress'>
+        <%=
+            image_tag(
+                "social/email.svg", 
+                alt: ""
+            )
+        %>
+    </div>
+    <div id='contact_confirmation'>
+        <p>
+            <%= t('contact.confirmation') %>
+        </p>
+        <div class='contact_buttons'>
+            <input type='button' value='<%=t("contact.close")%>' onClick='closeContactForm();'>
+        </div>
+    </div>
+    <script>
+        var form = document.getElementById('contact_form');
+        if (form.attachEvent) {
+            form.attachEvent("submit", sendMessage);
+        } else {
+            form.addEventListener("submit", sendMessage);
+        }
+    </script>
+</section>

+ 72 - 0
app/views/layouts/_footer.html.erb

@@ -0,0 +1,72 @@
+<footer>
+    <hr id='footer_separator'/>
+    <table id='footer_table'>
+        <tr>
+            <td id='footer_left'>
+                <span id='footer_follow' class='desktop'>
+                    <%= t("footer.follow") %>
+                </span>
+                <%=
+                    link_to(
+                        image_tag(
+                            "social/email.svg",
+                            class: "footer_social_icon",
+                            alt: t("social.mail")
+                        ),
+                        "javascript:showContactForm();",
+                        title: + t("index.email")
+                    )
+                %>
+                <% @user.user_social_links.each do |link| %>
+                    <%=
+                        link_to(
+                            image_tag(
+                                link.social_link.logo,
+                                class: "footer_social_icon",
+                                alt: link.social_link.i18n_link_title
+                            ),
+                            link.generate_link,
+                            target: "_blank",
+                            title: link.social_link.i18n_link_title
+                        )
+                    %>
+                <% end %>
+            </td>
+            <td id='footer_center'>
+                <%= t("footer.copy", name: @user.full_name, year: Date.today.year) %>
+            </td>
+            <td id='footer_right'>
+                <%= link_to(
+                    t("footer.help"),
+                    help_path,
+                    id: "footer_help",
+                    title: t("footer.help")
+                ) %>
+                <br/><br/>
+                <%
+                    path = request.fullpath
+                    if path.to_s[0..4] =~ /[a-z]{2}/ then
+                        path = path[3..] + "/"
+                    end
+                %>
+                <% I18n.available_locales.each do |locale| %>
+                    <%=
+                        link_to(
+                            image_tag(
+                                "lang/#{locale.to_s}.svg",
+                                alt: t("footer.lang_#{locale.to_s}")
+                            ),
+                            request.protocol +
+                                request.host_with_port +
+                                "/" +
+                                locale.to_s +
+                                path,
+                            class: "lang",
+                            title: t("footer.lang_#{locale.to_s}")
+                        )
+                    %>
+                <% end %>
+            </td>
+        </tr>
+    </table>
+</footer>

+ 34 - 0
app/views/layouts/_header.html.erb

@@ -0,0 +1,34 @@
+<header>
+    <div id='logo'>
+        inigoValentin
+    </div>
+    <nav>
+        <%=
+            link_to(
+                image_tag(
+                    "icon/home.svg",
+                    alt: ""
+                ) + t('header.home'),
+                main_path()
+            )
+        %>
+        <%=
+            link_to(
+                image_tag(
+                    "icon/profile.svg",
+                    alt: ""
+                ) + t('header.profile'),
+                profile_path()
+            )
+        %>
+        <%=
+            link_to(
+                image_tag(
+                    "icon/project.svg",
+                    alt: ""
+                ) + t('header.projects'),
+                project_index_url()
+            )
+        %>
+    </nav>
+</header>

+ 22 - 0
app/views/layouts/application.html.erb

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang='<%=I18n.locale%>'>
+    <head>
+        <title>InigoValentin</title>
+        <meta name='viewport' content='width=device-width,initial-scale=1'>
+        <%= csrf_meta_tags %>
+        <%= csp_meta_tag %>
+
+        <%= stylesheet_link_tag "ui", media: "all", "data-turbolinks-track": "reload" %>
+        <%= javascript_include_tag "main", "data-turbolinks-track" => true  %>
+        <%= yield(:head) %>
+    </head>
+
+    <body>
+        <%= render "layouts/header" %>
+        <main>
+            <%= yield %>
+        </main>
+        <%= render "layouts/footer" %>
+        <%= render "layouts/contact_form"%>
+  </body>
+</html>

+ 18 - 0
app/views/layouts/mailer.html.erb

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+        <style>
+            ul#details span{
+                font-weight: bold;
+            }
+            pre{
+                background-color: #333333;
+            }
+        </style>
+    </head>
+
+    <body>
+        <%= yield %>
+    </body>
+</html>

+ 1 - 0
app/views/layouts/mailer.text.erb

@@ -0,0 +1 @@
+<%= yield %>

+ 85 - 0
app/views/main/index.html.erb

@@ -0,0 +1,85 @@
+<% content_for :head do %>
+    <%= stylesheet_link_tag "main", media: "all", "data-turbolinks-track": "reload" %>
+<% end %>
+<section id='profile'>
+    <h3>
+        <%= @user.full_name %>
+    </h3>
+    <article>
+        <%= image_tag(@user.picture[0], id: "profile_image", alt: "")%>
+        <h4 id='tagline'>
+            <%= @user.i18n_tagline %>
+        </h4>
+        <p id='bio'>
+            <%= @user.i18n_bio %>
+        </p>
+        <div class='buttons'>
+            <%=
+                link_to(
+                    image_tag(
+                        "social/email.svg", 
+                        class: "footer_social_icon",
+                        alt: t("social.mail")
+                    ) + t("index.email"),
+                    "javascript:showContactForm()",
+                    class: "a_button"
+                )
+            %>
+            <%=
+                link_to(
+                    t("index.profile_more"),
+                    profile_path(),
+                    class: "a_button"
+                )
+            %>
+        </div>
+    </article>
+</section>
+<section id='projects'>
+    <h3>
+        <%= t("index.projects") %>
+    </h3>
+    <% @projects.each do |project| %>
+        <article class='project'>
+            <table>
+                <tr>
+                    <td class='icon'>
+                        <% if project.logo.attached? %>
+                            <%=
+                                link_to(
+                                    image_tag(
+                                        project.logo,
+                                        class: "project_image",
+                                        alt: project.i18n_title
+                                    ),
+                                    project_show_path(permalink: project.permalink)
+                            ) %>
+                        <% end %>
+                    </td>
+                    <td class='project_details'>
+                        <h4 class='project_name'>
+                            <%=
+                                link_to(
+                                    project.i18n_title,
+                                    project_show_path(permalink: project.permalink)
+                                )
+                            %>
+                        </h4>
+                        <span>
+                            <%= project.i18n_header %>
+                        </span>
+                    </td>
+                </tr>
+            </table>
+        </article>
+    <% end %>
+    <div class='buttons'>
+        <%=
+            link_to(
+                t("index.projects_all"),
+                project_index_url(),
+                class: "a_button"
+            )
+        %>
+    </div>
+</section>

+ 64 - 0
app/views/profile/index.html.erb

@@ -0,0 +1,64 @@
+<% content_for :head do %>
+    <%= stylesheet_link_tag "profile", media: "all", "data-turbolinks-track": "reload" %>
+<% end %>
+<section id='info'>
+    <h3>
+        <%= @user.full_name %>
+    </h3>
+    <article>
+        <h5>
+            <%=image_tag(@user.picture[0], id: "profile_image", alt: "")%>
+            <span>
+                <%= @user.i18n_tagline %>
+            </span>
+        </h5>
+        <div id='social'>
+            <% @user.user_social_links.each do |link| %>
+                <%= link_to(
+                    image_tag(link.social_link.logo, class: "project_image", alt: link.social_link.i18n_link_title, title: link.social_link.i18n_link_title),
+                    link.generate_link,
+                    target: "_blank",
+                    title: link.social_link.i18n_link_title
+                ) %>
+            <% end %>
+        </div>
+    </article>
+</section>
+<section id='about'>
+    <h3>
+        <%= t("profile.description") %>
+    </h3>
+    <article>
+        <p>
+            <%= @user.i18n_bio %>
+        </p>
+        <div id='resume_download'>
+            <%= link_to(
+                t("profile.resume"),
+                @resume_main.file,
+                target: "_blank",
+                title: t("profile.resume"),
+                class: "a_button"
+            ) %>
+            <% if @resume_languages.size > 0 %>
+                <details>
+                    <summary class='pointer'>
+                        <%= t("profile.resume_lang") %>
+                    </summary>
+                    <ul>
+                        <% @resume_languages.each do |resume| %>
+                            <li>
+                                <%= link_to(
+                                    t("profile.resume_" + resume.language),
+                                    resume.file,
+                                    target: "_blank",
+                                    title: t("profile.resume_" + resume.language)
+                                ) %>
+                            </li>
+                        <% end%>
+                    </ul>
+                </details>
+            <% end%>
+        </div>
+    </article>
+</section>

+ 43 - 0
app/views/projects/index.html.erb

@@ -0,0 +1,43 @@
+<% content_for :head do %>
+    <%= stylesheet_link_tag "projects", media: "all", "data-turbolinks-track": "reload" %>
+<% end %>
+<section>
+    <h3>
+        <%= t("project.title") %>
+    </h3>
+    <% @projects.each do |project| %>
+        <article class='project'>
+            <table>
+                <tr>
+                    <td class='logo'>
+                        <% if project.logo.attached? %>
+                            <%=
+                                link_to(
+                                    image_tag(
+                                        project.logo,
+                                        class: "project_image",
+                                        alt: project.i18n_title
+                                    ),
+                                    project_show_path(permalink: project.permalink)
+                                )
+                            %>
+                        <% end %>
+                    </td>
+                    <td class='project_details'>
+                        <h4 class='project_name'>
+                            <%=
+                                link_to(
+                                    project.i18n_title,
+                                    project_show_path(permalink: project.permalink)
+                                )
+                            %>
+                        </h4>
+                        <span>
+                            <%= project.i18n_header %>
+                        </span>
+                    </td>
+                </tr>
+            </table>
+        </article>
+    <% end %>
+</section>

+ 155 - 0
app/views/projects/show.html.erb

@@ -0,0 +1,155 @@
+<% content_for :head do %>
+    <%= stylesheet_link_tag "project", media: "all", "data-turbolinks-track": "reload" %>
+    <%= javascript_include_tag "project", "data-turbolinks-track" => true  %>
+<% end %>
+<section>
+    <h3>
+        <%= @project.i18n_title %>
+    </h3>
+    <article>
+        <div id='info_summary'>
+            <h4>
+                <%= @project.i18n_header %>
+            </h4>
+            <div id='text'>
+                <% if @project.logo.attached? %>
+                    <%=
+                        image_tag(
+                            @project.logo,
+                            class: "project_image",
+                            id: "project_image",
+                            alt: ""
+                        )
+                    %>
+                <% end %>
+                <%= @project.i18n_text.html_safe %>
+            </div>
+            <% if @project.comment != nil %>
+                <div id='comment'>
+                    <div id='comment_title'>
+                        <%= t("project.comment", name: @user.first_name) %>
+                    </div>
+                    <%= @project.i18n_comment.html_safe %>
+                </div>
+            <% end %>
+        </div>
+        <div id='info_details'>
+            <div id='links' class='details'>
+                <% @project.projectUrls.each do |url| %>
+                    <%=
+                        link_to(
+                            image_tag(
+                                url.urlTarget.logo,
+                                class: "link",
+                                title: url.urlTarget.i18n_summary,
+                                alt: url.urlTarget.i18n_summary
+                            ) + url.urlTarget.i18n_name,
+                            url.url,
+                            class: "link",
+                            target: "_blank",
+                            title: url.urlTarget.i18n_summary
+                    ) %>
+                <% end %>
+            </div>
+            <div id='technologies' class='details'>
+                <% @project.technologies.each do |technology| %>
+                    <span class='technology technology_<%=technology.nature%>'>
+                        <%= image_tag(technology.logo, class: "technology_logo", alt: "") %>
+                        <span class='technology_name'>
+                            <%= technology.name %>
+                        </span>
+                    </span>
+                <% end %>
+            </div>
+            <div id='license' class='details'>
+                <%= t("project.license") %>:
+                <%=
+                    link_to(
+                        image_tag(
+                            @project.license.logo,
+                            title: @project.license.name,
+                            alt: @project.license.name
+                        ),
+                        @project.license.url,
+                        target: "_blank",
+                        title: @project.license.name
+                    )
+                %>
+            </div> <!-- #license -->
+            <% if @project.images.attached? %>
+                <div id='images' class='details'>
+                    <%= t("project.images") %>
+                    <br/>
+                    <% i = 0 %>
+                    <% @project.images.each do |image| %>
+                        <%=
+                            link_to(
+                                image_tag(
+                                    image,
+                                    class: "project_image",
+                                    alt: ""
+                                ),
+                                "javascript:showImage(" + i.to_s + ");"
+                        ) %>
+                        <script>
+                            images[<%= i %>] = '<%= url_for(image) %>';
+                        </script>
+                        <% i = i + 1 %>
+                    <% end %>
+                </div>
+            <% end %>
+        </div> <!-- #info_details -->
+    </article>
+</section>
+<div id='gallery_bg'>
+</div>
+<section id='gallery'>
+    <h3>
+        <%= t("project.gallery_title", project: @project.i18n_title) %>
+        <span id='gallery_close'>
+            <%=
+                link_to(
+                    image_tag(
+                        "control/close.svg", 
+                        id: "gallery_close",
+                        alt: t("contact.cancel")
+                    ),
+                    #"mailto:" + @user.mail_adresses[0].address,
+                    "javascript:closeGallery()"
+                )
+            %>
+        </span>
+    </h3>
+    <article>
+        <div id='gallery_image_container'>
+            <img id='gallery_image'/>
+        </div>
+        <div id='gallery_controls'>
+            <div class='gallery_control' id='gallery_control_left'>
+                <input type='button' value='<' onClick='javascript:previousPhoto();'/>
+            </div>
+            <div id='image_list'>
+                <% i = 0 %>
+                <% @project.images.each do |image| %>
+                    <%=
+                        link_to(
+                            image_tag(
+                                image,
+                                id: "gallery_preview_" + i.to_s,
+                                class: "gallery_preview",
+                                alt: ""
+                            ),
+                            "javascript:showImage(" + i.to_s + ");"
+                    ) %>
+                    <script>
+                        images[<%= i %>] = '<%= url_for(image) %>';
+                    </script>
+                    <% i = i + 1 %>
+                <% end %>
+            </div>
+            <div class='gallery_control' id='gallery_control_right'>
+                <input type='button' value='>' onClick='javascript:nextPhoto();'/>
+            </div>
+        </div>
+    </article>
+</section>

+ 0 - 26
application/Application.php

@@ -1,26 +0,0 @@
-<?php
-/**
- * Constants file.
- *
- * Provides some predefined values.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package IV
- */
-
-    class URL{
-        //static $BASE = Net.get_protocol() . $_SERVER["HTTP_HOST"];
-        //static $STATIC = Net.get_protocol() . $_SERVER["HTTP_HOST"];
-        //static $BASE = ((1 == 1) ? 1 : 0);
-        //static $BASE = $_SERVER["HTTP_HOST"];
-    }
-    
-    final class EXCEPTION_CODE{
-        const PDO_UNSUPPORTED = 800001;
-        const PDO_MYSQL_HOSTNAME = 807701;
-        const PDO_MYSQL_DBNAME = 807702;
-        const PDO_MYSQL_PORT = 807703;
-    }
-
-?>

+ 0 - 68
application/Context.php

@@ -1,68 +0,0 @@
-<?php
-/**
- * Context file.
- *
- * Provides a handy class with usefull data to be accessible from anywhere in the app.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package IV
- */
-
-/**
- * Application context.
- *
- * Stores usefull data to be retrieved anywhere in the app.
- *
- * @category Context
- */
-class Context {
-
-    /**
-     * @var SQLITE3 Database connection.
-     */
-    private $db;
-
-    /**
-     * Retrieves the database connection.
-     *
-     * @return SQLITE3 The database connection.
-     */
-    public function get_db(){
-        return $this->db;
-    }
-
-    /**
-     * Sets the database connection
-     *
-     * @param SQLITE3 Database connection.
-     */
-    protected function set_db($db){
-        $this->db = $db;
-        Log::debug("Context database is set and accesible.");
-        if ($this->db == null){
-            Log::debug("It is null");
-        }
-    }
-    
-    /**
-     * Retrieves the language.
-     * 
-     * @todo Implement
-     * @return string Two letter language code.
-     */
-    public function get_lang(){
-        return "es";
-    }
-    
-    /**
-     * Retrieves the list of available languages.
-     *
-     * @todo Implement
-     * @return string Two letter language code.
-     */
-    public function get_available_langs(){
-        return [];
-    }
-
-}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác