diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8693b440..237c2da0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,6 @@ --- name: main -on: +on: push: branches: [ "master" ] pull_request: @@ -35,6 +35,12 @@ jobs: - name: Run rubocop run: bin/rubocop + - name: Run slim linter + run: bin/slim-lint app/views + + - name: Run scss linter + run: bin/scss-lint app/assets/stylesheets + - name: Run tests run: bin/rspec ... diff --git a/.scss-lint.yml b/.scss-lint.yml new file mode 100644 index 00000000..7c4dc324 --- /dev/null +++ b/.scss-lint.yml @@ -0,0 +1,12 @@ +scss_files: 'app/assets/stylesheets/**/*.scss' + +linters: + BorderZero: + enabled: false + + Indentation: + severity: warning + width: 2 + + PropertySortOrder: + enabled: false diff --git a/.slim-lint.yml b/.slim-lint.yml new file mode 100644 index 00000000..c07642fa --- /dev/null +++ b/.slim-lint.yml @@ -0,0 +1,36 @@ +linters: + LineLength: + max: 120 + + RedundantDiv: + enabled: false + + RuboCop: + enabled: true + # These cops are incredibly noisy since the Ruby we extract from Slim + # templates isn't well-formatted, so we ignore them. + ignored_cops: + - Layout/ArgumentAlignment + - Layout/HashAlignment + - Layout/IndentationWidth + - Layout/LineLength + - Layout/TrailingEmptyLines + - Lint/BlockAlignment + - Lint/EndAlignment + - Lint/Void + - Metrics/BlockLength + - Style/AlignParameters + - Style/BlockNesting + - Style/FileName + - Style/FirstParameterIndentation + - Style/FrozenStringLiteralComment + - Style/IfUnlessModifier + - Style/IndentationConsistency + - Style/IndentationWidth + - Style/Next + - Style/TrailingBlankLines + - Style/TrailingWhitespace + - Style/WhileUntilModifier + - Style/NestedTernaryOperator + - Style/StringLiterals + - Style/QuotedSymbols diff --git a/Gemfile b/Gemfile index 8a834768..77dbeb04 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,9 @@ gem "pg", "~> 1.1" gem "puma", "~> 5.0" # Use SCSS for stylesheets gem "sass-rails", ">= 6" +gem "scss_lint" +gem "slim_lint" +gem "slim-rails" # 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 diff --git a/Gemfile.lock b/Gemfile.lock index f1e1f7a8..88266a8a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,6 +278,11 @@ GEM ruby-next-core (0.15.3) ruby-progressbar (1.13.0) rubyzip (2.3.2) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -288,6 +293,8 @@ GEM sprockets (> 3.0) sprockets-rails tilt + scss_lint (0.60.0) + sass (~> 3.5, >= 3.5.5) selenium-webdriver (4.9.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) @@ -298,6 +305,16 @@ GEM connection_pool (>= 2.3.0) rack (>= 2.2.4) redis-client (>= 0.14.0) + slim (5.2.0) + temple (~> 0.10.0) + tilt (>= 2.1.0) + slim-rails (3.6.3) + actionpack (>= 3.1) + railties (>= 3.1) + slim (>= 3.0, < 6.0, != 5.0.0) + slim_lint (0.24.0) + rubocop (>= 1.0, < 2.0) + slim (>= 3.0, < 6.0) spring (4.1.1) sprockets (4.2.1) concurrent-ruby (~> 1.0) @@ -306,6 +323,7 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) + temple (0.10.3) thor (1.2.2) tilt (2.3.0) timeout (0.4.0) @@ -373,8 +391,11 @@ DEPENDENCIES rubocop-rake rubocop-thread_safety sass-rails (>= 6) + scss_lint selenium-webdriver (>= 4.0.0.rc1) sidekiq + slim-rails + slim_lint spring turbolinks (~> 5) tzinfo-data diff --git a/app/assets/stylesheets/_variables.scss b/app/assets/stylesheets/_variables.scss new file mode 100644 index 00000000..0febcab2 --- /dev/null +++ b/app/assets/stylesheets/_variables.scss @@ -0,0 +1,7 @@ +$black: #222; +$white: #fff; +$red: #f00; +$green: #008000; +$grey: #ccc; + +$header-bg: #f1f1f1; diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index b81139eb..3216ebd4 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -1,30 +1,12 @@ -.center { - text-align: center; -} -.record { - border: 1px solid #000000; - padding: 10px; - margin-bottom: 10px; -} -.flash-alert { - color: red; -} +// Bootstrap +@import 'bootstrap/scss/bootstrap'; -.flash-notice { - color: green; -} +@import 'variables'; +@import 'reset'; -.sort_links { - place-items: center; - display: flex; - margin-left: auto; - margin-right: auto; -} - -.link { - font-family: "Courier New"; - margin-left: auto; - margin-right: auto; -} +// Custom styles +@import 'base'; +@import 'header'; +@import 'form'; diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss new file mode 100644 index 00000000..3114d113 --- /dev/null +++ b/app/assets/stylesheets/base.scss @@ -0,0 +1,45 @@ +.flash-alert { + color: $red; +} + +.flash-notice { + color: $green; +} + +.sort-links { + display: flex; + gap: 1rem; + justify-content: flex-end; + margin: 1rem 0; + + font-size: .75rem; +} + +.link { + margin-left: auto; + margin-right: auto; +} + +.content { + width: 100%; + padding: 1rem 2rem; +} + +.title { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; +} + +.table-actions { + display: flex; + justify-content: end; + flex-wrap: wrap; + gap: 1rem; + width: 100%; +} + +.cursor-pointer { + cursor: pointer; +} diff --git a/app/assets/stylesheets/form.scss b/app/assets/stylesheets/form.scss new file mode 100644 index 00000000..8b451e36 --- /dev/null +++ b/app/assets/stylesheets/form.scss @@ -0,0 +1,15 @@ +.form { + display: flex; + flex-direction: column; + gap: 1rem; + max-width: 40rem; + + &__field { + width: 100%; + } + + &__actions { + display: flex; + justify-content: end; + } +} diff --git a/app/assets/stylesheets/header.scss b/app/assets/stylesheets/header.scss new file mode 100644 index 00000000..a9957c3d --- /dev/null +++ b/app/assets/stylesheets/header.scss @@ -0,0 +1,10 @@ +.header { + display: flex; + gap: 1rem; + justify-content: flex-end; + align-items: center; + + padding: 2rem; + background: $header-bg; + border-bottom: 1px solid $grey; +} diff --git a/app/assets/stylesheets/reset.scss b/app/assets/stylesheets/reset.scss new file mode 100644 index 00000000..d04e713a --- /dev/null +++ b/app/assets/stylesheets/reset.scss @@ -0,0 +1,40 @@ +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font-family: Trebuchet MS,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Tahoma,sans-serif; + vertical-align: baseline; +} + +footer, +header, +menu, +nav, +section { + display: block; +} + +body { + line-height: 1; +} + +ol, +ul { + list-style: none; +} + +blockquote, +q { + quotes: none; + + &::before, + &::after { + content: ''; + } +} diff --git a/app/javascript/components/script.js b/app/javascript/components/script.js new file mode 100644 index 00000000..228047d6 --- /dev/null +++ b/app/javascript/components/script.js @@ -0,0 +1,44 @@ +import _ from 'lodash'; + +class MyFunc { + constructor($el) { + this.$el = $el; + this.list = $el.querySelector('.js-projects-list'); + this.items = this.$el.querySelectorAll('.js-item'); + + this.sortAscButton = this.$el.querySelector('.js-sort-by-title-asc'); + this.sortDescButton = this.$el.querySelector('.js-sort-by-title-desc'); + + this.formattedItems = Array.from(this.items).map((item) => ({ + id: JSON.parse(item.getAttribute('data-id')), + name: item.getAttribute('data-name'), + node: item, + })); + + this.bindEvents(this.formattedItems); + } + + bindEvents() { + this.sortAscButton.addEventListener('click', this.onSortByTitleASC); + this.sortDescButton.addEventListener('click', this.onSortByTitleDESC); + } + + onSortByTitleASC = () => { + const sorteItemsByTitle = _.orderBy(this.formattedItems, ['name'], ['asc']); + + sorteItemsByTitle.forEach((item) => this.list.appendChild(item.node)); + this.sortAscButton.classList.add('d-none'); + this.sortDescButton.classList.remove('d-none'); + } + + onSortByTitleDESC = () => { + const sorteItemsByTitle = _.orderBy(this.formattedItems, ['name'], ['desc']); + + sorteItemsByTitle.forEach((item) => this.list.appendChild(item.node)); + + this.sortDescButton.classList.add('d-none'); + this.sortAscButton.classList.remove('d-none'); + } +} + +export default MyFunc; diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index f710851a..6c92f38b 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -8,6 +8,16 @@ import Turbolinks from "turbolinks" import * as ActiveStorage from "@rails/activestorage" import "channels" +import "bootstrap" + +import MyFunc from 'components/script' + Rails.start() Turbolinks.start() ActiveStorage.start() + +window.addEventListener("load", (event) => { + Array.from(document.querySelectorAll('.js-projects')).forEach(($el) => { + new MyFunc($el); + }); +}); diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb deleted file mode 100644 index 7f62610a..00000000 --- a/app/views/layouts/application.html.erb +++ /dev/null @@ -1,29 +0,0 @@ - - -
-<%= msg %>
-<%= project.id %>. <%= link_to project.name, project_tasks_path(project) %>
-<%= project.description %>
-<%= link_to "Edit", edit_project_path(project) %>
-<%= button_to "Destroy", project, method: :delete, data: {confirm: "Are you sure?"} %>
-Description: <%= @project.description %>
- -<%= link_to "New Task", new_project_task_path(@project), class: "btn btn-primary" %> -<%= link_to "Back", projects_path %> - -Name | -Description | -Status | -Deadline | -Actions | -
---|---|---|---|---|
<%= task.name %> | -<%= task.description %> | -<%= task.status ? "Completed" : "Not Completed" %> | -<%= task.deadline_at %> | -- <%= link_to "Show", project_task_path(@project, task), class: "btn btn-primary btn-sm" %> - <%= link_to "Edit", edit_project_task_path(@project, task), class: "btn btn-warning btn-sm" %> - <%= link_to "Destroy", project_task_path(@project, task), method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-danger btn-sm" %> - | -
- First Name: - <%= current_user.first_name %> -
- -- Last Name: - <%= current_user.last_name %> -
- -- Email: - <%= current_user.email %> -
- -<%= link_to "Go to Projects", projects_path %> diff --git a/app/views/sessions/show.html.slim b/app/views/sessions/show.html.slim new file mode 100644 index 00000000..918762b5 --- /dev/null +++ b/app/views/sessions/show.html.slim @@ -0,0 +1,15 @@ +h1 Your profile info + +p + strong First Name: + span = current_user.first_name + +p + strong Last Name: + span = current_user.last_name + +p + strong Email: + span = current_user.email + += link_to "Go to Projects", projects_path diff --git a/app/views/tasks/index.html.erb b/app/views/tasks/index.html.erb index 4f336b59..eb3784c9 100644 --- a/app/views/tasks/index.html.erb +++ b/app/views/tasks/index.html.erb @@ -2,7 +2,7 @@ <%= link_to "NEW TASK", new_project_task_path(@project) %> -<%= link_to "Name", :sort => "name asc"%>
<%= link_to "Name reverse", :sort => "name desc"%>
<%= link_to "Created_at", :sort => "id asc"%>
diff --git a/bin/scss-lint b/bin/scss-lint new file mode 100755 index 00000000..d195391b --- /dev/null +++ b/bin/scss-lint @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'scss-lint' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("scss_lint", "scss-lint") diff --git a/bin/slim-lint b/bin/slim-lint new file mode 100755 index 00000000..bec53d41 --- /dev/null +++ b/bin/slim-lint @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'slim-lint' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("slim_lint", "slim-lint") diff --git a/package.json b/package.json index 173afd35..ac00dd72 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,13 @@ "name": "task-tracker-itis", "private": true, "dependencies": { + "@popperjs/core": "^2.11.8", "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "5.4.4", + "bootstrap": "^5.3.2", + "lodash": "^4.17.21", "turbolinks": "^5.2.0", "webpack": "^4.46.0", "webpack-cli": "^3.3.12" diff --git a/yarn.lock b/yarn.lock index 9086098f..3fdfb222 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1031,6 +1031,11 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@rails/actioncable@^6.0.0": version "6.1.7" resolved "https://registry.npmjs.org/@rails/actioncable/-/actioncable-6.1.7.tgz#8b4506925d3f7146a70941e4db7ce9dda99f99ae" @@ -1673,6 +1678,11 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +bootstrap@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.2.tgz#97226583f27aae93b2b28ab23f4c114757ff16ae" + integrity sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4231,6 +4241,11 @@ lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.5: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + loglevel@^1.6.8: version "1.8.1" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4"