From dd7527691e7a165700c97de915ce6c9903461d98 Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Sun, 6 May 2018 19:07:41 +0200 Subject: [PATCH] =?UTF-8?q?Dammit=E2=80=A6=20I=20can't=20remember=20what?= =?UTF-8?q?=20I=20changed=20and=20why?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/javascripts/application.js | 1 - app/assets/stylesheets/_settings.scss | 338 ++++++++++++++++++--- app/assets/stylesheets/application.scss | 3 - app/assets/stylesheets/customisations.scss | 23 ++ app/controllers/application_controller.rb | 1 + app/controllers/packages_controller.rb | 226 ++++++++------ app/helpers/packages_helper.rb | 14 +- app/views/layouts/_messages.slim | 38 +-- app/views/layouts/_topbar.slim | 10 +- app/views/moderate/index.slim | 2 +- app/views/my/profile.slim | 18 +- app/views/packages/_admin_dropdown.slim | 60 ++-- app/views/packages/_receive_upload.slim | 22 ++ app/views/packages/_user_dropdown.slim | 30 +- app/views/packages/details.slim | 25 +- app/views/packages/list.slim | 2 +- app/views/packages/upload.slim | 2 + app/views/packages/upload_receive.slim | 78 ++--- app/views/welcome/home.slim | 3 +- config/routes.rb | 2 +- 20 files changed, 579 insertions(+), 319 deletions(-) create mode 100644 app/views/packages/_receive_upload.slim diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 51ec189..278ae21 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -15,7 +15,6 @@ //= require foundation //= require fancybox //= require cookies_eu -//= require jquery.amaran //= require_tree . $(function(){ $(document).foundation(); }); diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss index b6911de..e00bc13 100644 --- a/app/assets/stylesheets/_settings.scss +++ b/app/assets/stylesheets/_settings.scss @@ -21,25 +21,44 @@ // 16. Drilldown // 17. Dropdown // 18. Dropdown Menu -// 19. Forms -// 20. Label -// 21. Media Object -// 22. Menu -// 23. Meter -// 24. Off-canvas -// 25. Orbit -// 26. Pagination -// 27. Progress Bar -// 28. Responsive Embed -// 29. Reveal -// 30. Slider -// 31. Switch -// 32. Table -// 33. Tabs -// 34. Thumbnail -// 35. Title Bar -// 36. Tooltip -// 37. Top Bar +// 19. Flexbox Utilities +// 20. Forms +// 21. Label +// 22. Media Object +// 23. Menu +// 24. Meter +// 25. Off-canvas +// 26. Orbit +// 27. Pagination +// 28. Progress Bar +// 29. Prototype Arrow +// 30. Prototype Border-Box +// 31. Prototype Border-None +// 32. Prototype Bordered +// 33. Prototype Display +// 34. Prototype Font-Styling +// 35. Prototype List-Style-Type +// 36. Prototype Overflow +// 37. Prototype Position +// 38. Prototype Rounded +// 39. Prototype Separator +// 40. Prototype Shadow +// 41. Prototype Sizing +// 42. Prototype Spacing +// 43. Prototype Text-Decoration +// 44. Prototype Text-Transformation +// 45. Prototype Text-Utilities +// 46. Responsive Embed +// 47. Reveal +// 48. Slider +// 49. Switch +// 50. Table +// 51. Tabs +// 52. Thumbnail +// 53. Title Bar +// 54. Tooltip +// 55. Top Bar +// 56. Xy Grid @import 'util/util'; @@ -67,11 +86,16 @@ $body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; $body-antialiased: true; $global-margin: 1rem; $global-padding: 1rem; +$global-position: 1rem; $global-weight-normal: normal; $global-weight-bold: bold; $global-radius: 0; +$global-menu-padding: 0 1rem; +$global-menu-nested-margin: 1rem; $global-text-direction: ltr; -$global-flexbox: false; +$global-flexbox: true; +$global-prototype-breakpoints: false; +$global-color-pick-contrast-tolerance: 0; $print-transparent-backgrounds: true; @include add-foundation-colors; @@ -99,6 +123,7 @@ $grid-column-gutter: ( medium: 30px, ); $grid-column-align-edge: true; +$grid-column-alias: 'columns'; $block-grid-max: 8; // 4. Base Typography @@ -210,8 +235,17 @@ $accordion-content-padding: 1rem; // 8. Accordion Menu // ----------------- +$accordionmenu-padding: $global-menu-padding; +$accordionmenu-nested-margin: $global-menu-nested-margin; +$accordionmenu-submenu-padding: $accordionmenu-padding; $accordionmenu-arrows: true; $accordionmenu-arrow-color: $primary-color; +$accordionmenu-item-background: null; +$accordionmenu-border: null; +$accordionmenu-submenu-toggle-background: null; +$accordion-submenu-toggle-border: $accordionmenu-border; +$accordionmenu-submenu-toggle-width: 40px; +$accordionmenu-submenu-toggle-height: $accordionmenu-submenu-toggle-width; $accordionmenu-arrow-size: 6px; // 9. Badge @@ -235,11 +269,15 @@ $breadcrumbs-item-color-current: $black; $breadcrumbs-item-color-disabled: $medium-gray; $breadcrumbs-item-margin: 0.75rem; $breadcrumbs-item-uppercase: true; -$breadcrumbs-item-slash: true; +$breadcrumbs-item-separator: true; +$breadcrumbs-item-separator-item: '/'; +$breadcrumbs-item-separator-item-rtl: '\\'; +$breadcrumbs-item-separator-color: $medium-gray; // 11. Button // ---------- +$button-font-family: inherit; $button-padding: 0.85em 1em; $button-margin: 0 0 $global-margin 0; $button-fill: solid; @@ -248,6 +286,7 @@ $button-background-hover: scale-color($button-background, $lightness: -15%); $button-color: $white; $button-color-alt: $black; $button-radius: $global-radius; +$button-hollow-border-width: 1px; $button-sizes: ( tiny: 0.6rem, small: 0.75rem, @@ -292,7 +331,7 @@ $card-border: 1px solid $light-gray; $card-shadow: none; $card-border-radius: $global-radius; $card-padding: $global-padding; -$card-margin: $global-margin; +$card-margin-bottom: $global-margin; // 15. Close Button // ---------------- @@ -319,9 +358,13 @@ $closebutton-color-hover: $black; $drilldown-transition: transform 0.15s linear; $drilldown-arrows: true; +$drilldown-padding: $global-menu-padding; +$drilldown-nested-margin: 0; +$drilldown-background: $white; +$drilldown-submenu-padding: $drilldown-padding; +$drilldown-submenu-background: $white; $drilldown-arrow-color: $primary-color; $drilldown-arrow-size: 6px; -$drilldown-background: $white; // 17. Dropdown // ------------ @@ -344,11 +387,24 @@ $dropdown-sizes: ( $dropdownmenu-arrows: true; $dropdownmenu-arrow-color: $anchor-color; $dropdownmenu-arrow-size: 6px; +$dropdownmenu-arrow-padding: 1.5rem; $dropdownmenu-min-width: 200px; $dropdownmenu-background: $white; +$dropdownmenu-submenu-background: $dropdownmenu-background; +$dropdownmenu-padding: $global-menu-padding; +$dropdownmenu-nested-margin: 0; +$dropdownmenu-submenu-padding: $dropdownmenu-padding; $dropdownmenu-border: 1px solid $medium-gray; +$dropdown-menu-item-color-active: get-color(primary); +$dropdown-menu-item-background-active: transparent; -// 19. Forms +// 19. Flexbox Utilities +// --------------------- + +$flex-source-ordering-count: 6; +$flexbox-responsive-breakpoints: true; + +// 20. Forms // --------- $fieldset-border: 1px solid $medium-gray; @@ -375,11 +431,13 @@ $input-placeholder-color: $medium-gray; $input-font-family: inherit; $input-font-size: rem-calc(16); $input-font-weight: $global-weight-normal; +$input-line-height: $global-lineheight; $input-background: $white; $input-background-focus: $white; $input-background-disabled: $light-gray; $input-border: 1px solid $medium-gray; $input-border-focus: 1px solid $dark-gray; +$input-padding: $form-spacing / 2; $input-shadow: inset 0 1px 2px rgba($black, 0.1); $input-shadow-focus: 0 0 5px $medium-gray; $input-cursor-disabled: not-allowed; @@ -388,7 +446,7 @@ $input-number-spinners: true; $input-radius: $global-radius; $form-button-radius: $global-radius; -// 20. Label +// 21. Label // --------- $label-background: $primary-color; @@ -399,26 +457,28 @@ $label-font-size: 0.8rem; $label-padding: 0.33333rem 0.5rem; $label-radius: $global-radius; -// 21. Media Object +// 22. Media Object // ---------------- $mediaobject-margin-bottom: $global-margin; $mediaobject-section-padding: $global-padding; $mediaobject-image-width-stacked: 100%; -// 22. Menu +// 23. Menu // -------- $menu-margin: 0; -$menu-margin-nested: 1rem; -$menu-item-padding: 0.7rem 1rem; +$menu-nested-margin: $global-menu-nested-margin; +$menu-items-padding: $global-menu-padding; +$menu-simple-margin: 1rem; $menu-item-color-active: $white; $menu-item-background-active: get-color(primary); $menu-icon-spacing: 0.25rem; $menu-item-background-hover: $light-gray; -$menu-border: $light-gray; +$menu-state-back-compat: true; +$menu-centered-back-compat: true; -// 23. Meter +// 24. Meter // --------- $meter-height: 1rem; @@ -428,23 +488,26 @@ $meter-fill-good: $success-color; $meter-fill-medium: $warning-color; $meter-fill-bad: $alert-color; -// 24. Off-canvas +// 25. Off-canvas // -------------- $offcanvas-size: 250px; $offcanvas-vertical-size: 250px; $offcanvas-background: $light-gray; $offcanvas-shadow: 0 0 10px rgba($black, 0.7); -$offcanvas-push-zindex: 1; -$offcanvas-overlap-zindex: 10; -$offcanvas-reveal-zindex: 1; +$offcanvas-inner-shadow-size: 20px; +$offcanvas-inner-shadow-color: rgba($black, 0.25); +$offcanvas-overlay-zindex: 11; +$offcanvas-push-zindex: 12; +$offcanvas-overlap-zindex: 13; +$offcanvas-reveal-zindex: 12; $offcanvas-transition-length: 0.5s; $offcanvas-transition-timing: ease; $offcanvas-fixed-reveal: true; $offcanvas-exit-background: rgba($white, 0.25); $maincontent-class: 'off-canvas-content'; -// 25. Orbit +// 26. Orbit // --------- $orbit-bullet-background: $medium-gray; @@ -459,7 +522,7 @@ $orbit-control-background-hover: rgba($black, 0.5); $orbit-control-padding: 1rem; $orbit-control-zindex: 10; -// 26. Pagination +// 27. Pagination // -------------- $pagination-font-size: rem-calc(14); @@ -477,7 +540,7 @@ $pagination-mobile-items: false; $pagination-mobile-current-item: false; $pagination-arrows: true; -// 27. Progress Bar +// 28. Progress Bar // ---------------- $progress-height: 1rem; @@ -486,7 +549,168 @@ $progress-margin-bottom: $global-margin; $progress-meter-background: $primary-color; $progress-radius: $global-radius; -// 28. Responsive Embed +// 29. Prototype Arrow +// ------------------- + +$prototype-arrow-directions: ( + down, + up, + right, + left +); +$prototype-arrow-size: 0.4375rem; +$prototype-arrow-color: $black; + +// 30. Prototype Border-Box +// ------------------------ + +$prototype-border-box-breakpoints: $global-prototype-breakpoints; + +// 31. Prototype Border-None +// ------------------------- + +$prototype-border-none-breakpoints: $global-prototype-breakpoints; + +// 32. Prototype Bordered +// ---------------------- + +$prototype-bordered-breakpoints: $global-prototype-breakpoints; +$prototype-border-width: rem-calc(1); +$prototype-border-type: solid; +$prototype-border-color: $medium-gray; + +// 33. Prototype Display +// --------------------- + +$prototype-display-breakpoints: $global-prototype-breakpoints; +$prototype-display: ( + inline, + inline-block, + block, + table, + table-cell +); + +// 34. Prototype Font-Styling +// -------------------------- + +$prototype-font-breakpoints: $global-prototype-breakpoints; +$prototype-wide-letter-spacing: rem-calc(4); +$prototype-font-normal: $global-weight-normal; +$prototype-font-bold: $global-weight-bold; + +// 35. Prototype List-Style-Type +// ----------------------------- + +$prototype-list-breakpoints: $global-prototype-breakpoints; +$prototype-style-type-unordered: ( + disc, + circle, + square +); +$prototype-style-type-ordered: ( + decimal, + lower-alpha, + lower-latin, + lower-roman, + upper-alpha, + upper-latin, + upper-roman +); + +// 36. Prototype Overflow +// ---------------------- + +$prototype-overflow-breakpoints: $global-prototype-breakpoints; +$prototype-overflow: ( + visible, + hidden, + scroll +); + +// 37. Prototype Position +// ---------------------- + +$prototype-position-breakpoints: $global-prototype-breakpoints; +$prototype-position: ( + static, + relative, + absolute, + fixed +); +$prototype-position-z-index: 975; + +// 38. Prototype Rounded +// --------------------- + +$prototype-rounded-breakpoints: $global-prototype-breakpoints; +$prototype-border-radius: rem-calc(3); + +// 39. Prototype Separator +// ----------------------- + +$prototype-separator-breakpoints: $global-prototype-breakpoints; +$prototype-separator-align: center; +$prototype-separator-height: rem-calc(2); +$prototype-separator-width: 3rem; +$prototype-separator-background: $primary-color; +$prototype-separator-margin-top: $global-margin; + +// 40. Prototype Shadow +// -------------------- + +$prototype-shadow-breakpoints: $global-prototype-breakpoints; +$prototype-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), + 0 2px 10px 0 rgba(0,0,0,.12); + +// 41. Prototype Sizing +// -------------------- + +$prototype-sizing-breakpoints: $global-prototype-breakpoints; +$prototype-sizing: ( + width, + height +); +$prototype-sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100% +); + +// 42. Prototype Spacing +// --------------------- + +$prototype-spacing-breakpoints: $global-prototype-breakpoints; +$prototype-spacers-count: 3; + +// 43. Prototype Text-Decoration +// ----------------------------- + +$prototype-decoration-breakpoints: $global-prototype-breakpoints; +$prototype-text-decoration: ( + overline, + underline, + line-through, +); + +// 44. Prototype Text-Transformation +// --------------------------------- + +$prototype-transformation-breakpoints: $global-prototype-breakpoints; +$prototype-text-transformation: ( + lowercase, + uppercase, + capitalize +); + +// 45. Prototype Text-Utilities +// ---------------------------- + +$prototype-utilities-breakpoints: $global-prototype-breakpoints; +$prototype-text-overflow: ellipsis; + +// 46. Responsive Embed // -------------------- $responsive-embed-margin-bottom: rem-calc(16); @@ -495,7 +719,7 @@ $responsive-embed-ratios: ( widescreen: 16 by 9, ); -// 29. Reveal +// 47. Reveal // ---------- $reveal-background: $white; @@ -507,7 +731,7 @@ $reveal-radius: $global-radius; $reveal-zindex: 1005; $reveal-overlay-background: rgba($black, 0.45); -// 30. Slider +// 48. Slider // ---------- $slider-width-vertical: 0.5rem; @@ -521,7 +745,7 @@ $slider-handle-background: $primary-color; $slider-opacity-disabled: 0.25; $slider-radius: $global-radius; -// 31. Switch +// 49. Switch // ---------- $switch-background: $medium-gray; @@ -537,7 +761,7 @@ $switch-paddle-offset: 0.25rem; $switch-paddle-radius: $global-radius; $switch-paddle-transition: all 0.25s ease-out; -// 32. Table +// 50. Table // --------- $table-background: $white; @@ -557,8 +781,9 @@ $table-foot-row-hover: darken($table-foot-background, $table-hover-scale); $table-head-font-color: $body-font-color; $table-foot-font-color: $body-font-color; $show-header-for-stacked: false; +$table-stack-breakpoint: medium; -// 33. Tabs +// 51. Tabs // -------- $tab-margin: 0; @@ -575,7 +800,7 @@ $tab-content-border: $light-gray; $tab-content-color: $body-font-color; $tab-content-padding: 1rem; -// 34. Thumbnail +// 52. Thumbnail // ------------- $thumbnail-border: solid 4px $white; @@ -585,7 +810,7 @@ $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); $thumbnail-transition: box-shadow 200ms ease-out; $thumbnail-radius: $global-radius; -// 35. Title Bar +// 53. Title Bar // ------------- $titlebar-background: $black; @@ -596,20 +821,22 @@ $titlebar-icon-color: $white; $titlebar-icon-color-hover: $medium-gray; $titlebar-icon-spacing: 0.25rem; -// 36. Tooltip +// 54. Tooltip // ----------- +$has-tip-cursor: help; $has-tip-font-weight: $global-weight-bold; $has-tip-border-bottom: dotted 1px $dark-gray; $tooltip-background-color: $black; $tooltip-color: $white; $tooltip-padding: 0.75rem; +$tooltip-max-width: 10rem; $tooltip-font-size: $small-font-size; $tooltip-pip-width: 0.75rem; $tooltip-pip-height: $tooltip-pip-width * 0.866; $tooltip-radius: $global-radius; -// 37. Top Bar +// 55. Top Bar // ----------- $topbar-padding: 0.5rem; @@ -618,3 +845,18 @@ $topbar-submenu-background: $topbar-background; $topbar-title-spacing: 0.5rem 1rem 0.5rem 0; $topbar-input-width: 200px; $topbar-unstack-breakpoint: medium; + +// 56. Xy Grid +// ----------- + +$xy-grid: true; +$grid-container: $global-width; +$grid-columns: 12; +$grid-margin-gutters: ( + small: 20px, + medium: 30px +); +$grid-padding-gutters: $grid-margin-gutters; +$grid-container-padding: $grid-padding-gutters; +$grid-container-max: $global-width; +$block-grid-max: 8; diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 246b8d8..5f83651 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -12,8 +12,5 @@ *= require foundation_and_overrides *= require cookies_eu *= require fancybox - * Flash/notifications using http://www.amaranjs.com/ - * https://github.com/hakanersu/AmaranJS - *= require amaran *= require font-awesome */ diff --git a/app/assets/stylesheets/customisations.scss b/app/assets/stylesheets/customisations.scss index 12e4ac4..56a6e2e 100644 --- a/app/assets/stylesheets/customisations.scss +++ b/app/assets/stylesheets/customisations.scss @@ -370,3 +370,26 @@ a.black // font-weight: bold; line-height: 1; } + +.button { + box-shadow: 0px 0px 5px #000000; + border-radius: 10px !important; +} + +// Simple animation for FontAwesome icons in callouts at the top. +.fa-wobble { + animation-name: fa-wobble; + animation-duration: 0.5s; + animation-direction: alternate; + animation-iteration-count: infinite; +} + +@keyframes fa-wobble { + 0% { opacity: 0.2; } + 50% { opacity: 0.8; } + 100% { opacity: 1; } +} + +.menu .menu-text { + font-size: 2.5rem; +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c40f76c..212b556 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base # filter_parameter_logging :password before_action :get_current_users_screenshots, :debian_sso + # TODO: Deprecation. What is the replacement for Rails.env.development? ? before_action :better_errors_hack, if: -> { Rails.env.development? } # Query for packages that were uploaded by the current user. diff --git a/app/controllers/packages_controller.rb b/app/controllers/packages_controller.rb index 03c56a3..5870c95 100644 --- a/app/controllers/packages_controller.rb +++ b/app/controllers/packages_controller.rb @@ -1,29 +1,30 @@ class PackagesController < ApplicationController - + protect_from_forgery :except => :legacy_uploadfile - + def list @packages = query_packages.paginate(page: params[:page], per_page: 6) end - + def grid @packages = query_packages.paginate(page: params[:page], per_page: 24) end - + def details + # TODO: Get only screenshots visible to the user (admin or owner or approved) @package = Package.find_by(name: params[:name]) - + unless @package @packagename = params[:name] render 'notfound' end end - + # Show upload form for new images def upload @package = Package.find_by!(name: params[:name]) end - + # POST target of the screenshots upload form. # Receives uploaded images. Checks if they are valid. Asks for description. # This action saves the screenshots already if they are valid. The user is @@ -33,43 +34,63 @@ class PackagesController < ApplicationController # The user won't know that an account is created. # But this makes it easier to track who screenshots belong to. create_pseudo_user unless user_signed_in? - + @package = Package.find_by!(name: params[:name]) @valid_images = [] @invalid_images = [] - + params[:file].each do |img| new_screenshot = @package.screenshots.new(image: img) - + # Check if the image was valid if new_screenshot.valid? new_screenshot.uploaderhash = session[:token] new_screenshot.uploaderip = session[:ip] new_screenshot.version = @package.version new_screenshot.user = current_user - + # TODO: must it be moderated first? - + new_screenshot.save Log.log "Screenshot #{new_screenshot.id} uploaded successfully from #{session[:ip]}. User has token #{session[:token]} or is #{current_user}" + + if current_user.is_admin? + Log.log "Admin upload is automatically approved." + new_screenshot.approve_screenshot! + end + @valid_images.push new_screenshot else Log.log "Screenshot #{new_screenshot.image_file_name} invalid (#{new_screenshot.errors[:image]})." @invalid_images.push new_screenshot end end - + + errors = [] + @invalid_images.each do |image| + # errors << "The image #{image.image_file_name} #{image.errors[:image].join(' and ')}." + errors << "The image #{image.image_file_name} is not valid." + flash[:error] = errors.join(" ") + end + + # Redirect back to upload form if all uploads were invalid + unless @valid_images.any? + Log.log "No valid images uploaded. Back to upload form." + redirect_to(upload_path, error: errors) and return + end + # Show a list of invalid uploads by default. Or redirect to the review page # if all uploads were okay. - redirect_to upload_review_path unless @invalid_images - + # redirect_to upload_review_path unless @invalid_images + # TODO # if @invalid_images… # ' #{image.image_file_name} (#{image.errors[:image].join(' and ')}) - - # redirect_to package_path + + redirect_to package_path + # render :details end - + # Legacy action to upload an image along with metadata. # This was used in Debshots 1.x as the default upload method. # This method allows that old-style way to upload screenshots. @@ -82,9 +103,9 @@ class PackagesController < ApplicationController # - file def legacy_uploadfile @package = Package.find_by!(name: params[:packagename]) - + new_screenshot = @package.screenshots.new(image: params[:file]) - + # Check if the image was valid if new_screenshot.valid? # new_screenshot.uploaderhash = session[:token] @@ -98,30 +119,30 @@ class PackagesController < ApplicationController head :not_acceptable end end - + def delete_screenshot # Is the user allowed to delete the screenshot? @screenshot = Screenshot.find(params[:id]) - + # Check if the user is allowed to change this screenshot # - Is this the user's own screenshot? (anonymous) if @screenshot.user == current_user or current_user.is_admin? logger.debug "User #{current_user} deletes screenshot #{@screenshot}" @screenshot.destroy flash['notice'] = "Screenshot deleted." - redirect_to :back + redirect_back(fallback_location: package_path(name: @screenshot.package.name)) else head :forbidden end end - + def approve_screenshot @screenshot = Screenshot.find(params[:id]) @screenshot.approve_screenshot! flash['notice'] = "Screenshot approved." - redirect_to :back + redirect_back(fallback_location: package_path) end - + # Returns a 160x120 thumbnail image if posssible. # If the package is not found it returns a dummy image along with status 404. # If the package is found but has no screenshots then it also returns a @@ -132,25 +153,25 @@ class PackagesController < ApplicationController thumbnail404 return end - + # Called as /thumbnail-with-version/:name/:version if params[:version] @screenshot = @package.best_screenshot_for_version(params[:version]) - # Called as /thumbnail/:name + # Called as /thumbnail/:name else @screenshot = @package.screenshots.first end - + # Return a 404 if the package has no screenshots or the image was not found unless @screenshot and @screenshot.image.path thumbnail404 return end - + # Send the thumbnail (uses X-Sendfile or similar if possible) send_file @screenshot.image.path(:thumb), type: "image/png", disposition: 'inline' end - + # Returns a large screenshot image if posssible. # If the package is not found it returns a dummy image along with status 404. # If the package is found but has no screenshots then it also returns a @@ -161,39 +182,41 @@ class PackagesController < ApplicationController screenshot404 return end - + # Called as /thumbnail-with-version/:name/:version if params[:version] @screenshot = @package.best_screenshot_for_version(params[:version]) - # Called as /thumbnail/:name + # Called as /thumbnail/:name else @screenshot = @package.screenshots.first end - + # Return a 404 if the package has no screenshots or the image was not found unless @screenshot and @screenshot.image.path screenshot404 return end - + # Send the thumbnail (uses X-Sendfile or similar if possible) send_file @screenshot.image.path(:large), type: "image/png", disposition: 'inline' end - + # Receives a form with a simple text field 'description' so that users can update # the description of their screenshot. def update_screenshot_description - @screenshot = Screenshot.find(params[:id]) - @screenshot.description = params[:description] - @screenshot.save! - flash['notice'] = "Description updated." - redirect_to :back - end - - # Receive an anonymous report from a user to have a screenshot removed. - def report_screenshot - @screenshot = Screenshot.find(params[:id]) - # if verify_recaptcha + # TODO: Check permissions to do that + @screenshot = Screenshot.find(params[:id]) + # @screenshot.description = params[:description] + @screenshot.update params_screenshot_description + @screenshot.save! + flash['notice'] = "Description updated." + redirect_back(fallback_location: package_path(name: @screenshot.package.name)) + end + + # Receive an anonymous report from a user to have a screenshot removed. + def report_screenshot + @screenshot = Screenshot.find(params[:id]) + # if verify_recaptcha @screenshot.delete_reason = params[:delete_reason] @screenshot.markedfordelete = true if @screenshot.valid? @@ -203,57 +226,57 @@ class PackagesController < ApplicationController errors = @screenshot.errors.to_a.join(' and ') flash['alert'] = "Sorry. #{errors}" end - # end - redirect_to :back - end - - # Show an HTML partial with reviews of this package from the Ubuntu API - def reviews - expires_in 1.day, public: true - # @reviews = Package.find_by_name!(params[:name]).ubuntu_reviews - @reviews = get_ubuntu_reviews params[:name] - render '_reviews', layout: false - end - - private - - # Seamlessly create a user account for the current client. - # It helps track uploads. - def create_pseudo_user - generated_password = Devise.friendly_token.first(8) - new_user = User.create( + # end + redirect_back(fallback_location: package_path) + end + + # Show an HTML partial with reviews of this package from the Ubuntu API + def reviews + expires_in 1.day, public: true + # @reviews = Package.find_by_name!(params[:name]).ubuntu_reviews + @reviews = get_ubuntu_reviews params[:name] + render '_reviews', layout: false + end + + private + + # Seamlessly create a user account for the current client. + # It helps track uploads. + def create_pseudo_user + generated_password = Devise.friendly_token.first(8) + new_user = User.create( name: 'Anonymous', password: generated_password) - Log.log "New pseudo user for anonymous upload created: #{new_user}" - sign_in(new_user) - end - - # Send a dummy thumbnail reading "No screenshot available. Sorry." - def thumbnail404 - send_file Rails.root.join('public/images/dummy/thumbnail404.png'), - type: "image/png", - disposition: 'inline', - status: 404 - end - - def screenshot404 - send_file Rails.root.join('public/images/dummy/screenshot404.png'), - type: "image/png", - disposition: 'inline', - status: 404 - end - - # Return packages matching the criteria given by parameters - def query_packages - packages = Package.includes(:screenshots).order(visits: :desc) - - # text search - if params[:search].present? - logger.debug "Searching for #{params[:search]}" - packages = packages.general_search(params[:search]) + Log.log "New pseudo user for anonymous upload created: #{new_user}" + sign_in(new_user) end - - case params[:show] + + # Send a dummy thumbnail reading "No screenshot available. Sorry." + def thumbnail404 + send_file Rails.root.join('public/images/dummy/thumbnail404.png'), + type: "image/png", + disposition: 'inline', + status: 404 + end + + def screenshot404 + send_file Rails.root.join('public/images/dummy/screenshot404.png'), + type: "image/png", + disposition: 'inline', + status: 404 + end + + # Return packages matching the criteria given by parameters + def query_packages + packages = Package.includes(:screenshots).order(visits: :desc) + + # text search + if params[:search].present? + logger.debug "Searching for #{params[:search]}" + packages = packages.general_search(params[:search]) + end + + case params[:show] when 'with' packages = packages.with_screenshots logger.debug 'Limiting packages to those with screenshots' @@ -261,23 +284,23 @@ class PackagesController < ApplicationController packages = packages.without_screenshots logger.debug 'Limiting packages to those without screenshots' end - + return packages end - + # Store a random identifier and the client's IP address in the session # for later identification. def create_user_token session[:token] ||= SecureRandom.hex session[:ip] ||= request.remote_ip end - + # Get reviews of this package from the Ubuntu API def get_ubuntu_reviews(packagename) # Use the URL defined in the configuration to get a JSON string url = Rails.configuration.ubuntu_reviews_api_url % packagename logger.debug "Loading Ubuntu reviews for package #{packagename} from #{url}" - + body = open(url).read # Turn JSON into a Ruby data structure json = JSON.parse(body) @@ -288,5 +311,8 @@ class PackagesController < ApplicationController json = json.sort { |x,y| y['usefulness_total'].to_i <=> x['usefulness_total'].to_i} return json end - + + def params_screenshot_description + params.require(:screenshot).permit(:description) + end end diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index 3cae56c..123181d 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -3,16 +3,16 @@ module PackagesHelper # Return a query of all screenshots that the current user may see # Consists of: # - approved (public) screenshots - # - screenshots uploaded by the user (determined by cookie session) - # - all screenshots if the user is logged in + # - screenshots uploaded by the user + # - all screenshots if the user is an admin def screenshots_visible_to_user(package) - if user_signed_in? + if user_signed_in? and current_user.is_admin? # User is an admin - package.screenshots.order('created_at DESC') + package.screenshots + elsif user_signed_in? + current_user.screenshots else - package.screenshots.where( - "approved=true OR uploaderhash=?", session[:token] - ).order('created_at DESC') + package.screenshots.where(approved: true).order('created_at DESC') end end diff --git a/app/views/layouts/_messages.slim b/app/views/layouts/_messages.slim index ba6aa55..9d3fd1e 100644 --- a/app/views/layouts/_messages.slim +++ b/app/views/layouts/_messages.slim @@ -3,26 +3,18 @@ - if flash - flash.each do |name,msg| - case name - - when 'notice' - - flash_color = 'white' - - flash_bgcolor = 'green' - - when 'alert' - - flash_color = 'white' - - flash_bgcolor = 'red' - - when 'timedout' - - next - - else - - flash_color = 'black' - - flash_bgcolor = 'gray' - - javascript: - $.amaran({ - 'content': { - 'message' :'#{msg}', - 'color' : '#{flash_color}', - 'bgcolor' : '#{flash_bgcolor}' - }, - 'position' :'bottom right', - 'theme' : 'colorful', - 'delay' : 7000 - }); + - when 'timedout' + / Workaround for Devise sometimes sending "true" + - next + - when 'notice' + .callout.warning data-closable=true + = fa_icon 'info-circle wobble 2x' + strong< = msg + - when 'error' + .callout.alert data-closable=true + = fa_icon 'meh-o wobble 2x' + strong< = msg + - else + .callout.primary data-closable=true + = fa_icon 'info-circle wobble 2x' + strong< = msg diff --git a/app/views/layouts/_topbar.slim b/app/views/layouts/_topbar.slim index ff49b59..1810df5 100644 --- a/app/views/layouts/_topbar.slim +++ b/app/views/layouts/_topbar.slim @@ -1,14 +1,14 @@ -nav.top-bar - .row - .top-bar-left - .top-bar-title +.top-bar + .top-bar-left + ul.menu + li.menu-text a href='/' // TODO: Make the logo depend on the virtual host img src="/images/logos/debian.svg" width="35" alt="Debian logo" // TODO: Use proper distribution name ' Debian Screenshots > - .top-bar-right + .top-bar-right ul.dropdown.menu data-dropdown-menu=true li class=('active' if controller_name=='welcome' and action_name=='home') a href="/" Home diff --git a/app/views/moderate/index.slim b/app/views/moderate/index.slim index 00cacf3..a6a28ef 100644 --- a/app/views/moderate/index.slim +++ b/app/views/moderate/index.slim @@ -17,7 +17,7 @@ .small-6.columns p Please moderate: - @pending_screenshots.each do |screenshot| - .container.listview.text-center + .container.listview / = screenshot.status a.black.fancybox href=screenshot.image.url(:large, timestamp: false) rel='fancybox-thumb' title=screenshot.caption = image_tag(screenshot.image.url(:large, timestamp: false), alt: screenshot.caption) diff --git a/app/views/my/profile.slim b/app/views/my/profile.slim index 007f823..58c199e 100644 --- a/app/views/my/profile.slim +++ b/app/views/my/profile.slim @@ -13,14 +13,16 @@ / TODO: submit - save - h2 Your email address - p = current_user.email - p.help-text - ' This web site recognizes you by your email address. - ' Don't worry - it will not be shown or given to anyone. - - h2 Single-sign-on provider - p You logged in using #{current_user.pretty_provider}. + - unless current_user.email.blank? + h2 Your email address + p = current_user.email + p.help-text + ' This web site recognizes you by your email address. + ' Don't worry - it will not be shown or given to anyone. + + - unless current_user.provider.blank? + h2 Single-sign-on provider + p You logged in using #{current_user.pretty_provider}. h2 Number of screenshots you uploaded p = current_user.screenshots.count diff --git a/app/views/packages/_admin_dropdown.slim b/app/views/packages/_admin_dropdown.slim index cc98e5b..6a966e5 100644 --- a/app/views/packages/_admin_dropdown.slim +++ b/app/views/packages/_admin_dropdown.slim @@ -1,35 +1,33 @@ // Button that reveals a dropdown/modal for moderation/reporting -.button-bar - ul.button-group - - if not screenshot.approved - li - a.button.small.success[ - href=approve_screenshot_path(screenshot.id) - method='post' - ] Approve screenshot - - elsif screenshot.markedfordelete - li - a.button.small.success[ - href=approve_screenshot_path(screenshot.id) - method='post' - ] Keep screenshot +button-group + - if user_signed_in? + a.button.small.alert[ + href=delete_screenshot_path(screenshot.id) + onclick="return confirm('Really delete the screenshot?');" + ] Delete screenshot - - if user_signed_in? - li - a.button.small.alert[ - href=delete_screenshot_path(screenshot.id) - onclick="return confirm('Really delete the screenshot?');" - ] Delete screenshot - / Display additional information to admins - p Status: #{screenshot.adminstatus} - p Uploader IP=#{screenshot.uploaderip} - p Uploader Token=#{session[:token]} - p Uploaded #{screenshot.age_days} (#{screenshot.created_at}) + - if not screenshot.approved + a.button.small.success[ + href=approve_screenshot_path(screenshot.id) + method='post' + ] Approve screenshot + - elsif screenshot.markedfordelete + a.button.small.success[ + href=approve_screenshot_path(screenshot.id) + method='post' + ] Keep screenshot - / TODO: Move reporting screenshots to an extra page with a form and captcha - / - else - / / Allow anonymous users to report inappropriate screenshots - / = form_tag(report_screenshot_path(screenshot.id)) - / = text_area_tag 'delete_reason', nil, class: 'input-group-field', maxlength: 100, rows: 3, cols: 50 - / = submit_tag 'Request removal', class: 'button alert' + + / Display additional information to admins + p Status: #{screenshot.adminstatus} + p Uploader IP=#{screenshot.uploaderip} + p Uploader Token=#{session[:token]} + p Uploaded #{screenshot.age_days} (#{screenshot.created_at}) + + / TODO: Move reporting screenshots to an extra page with a form and captcha + / - else + / / Allow anonymous users to report inappropriate screenshots + / = form_tag(report_screenshot_path(screenshot.id)) + / = text_area_tag 'delete_reason', nil, class: 'input-group-field', maxlength: 100, rows: 3, cols: 50 + / = submit_tag 'Request removal', class: 'button alert' diff --git a/app/views/packages/_receive_upload.slim b/app/views/packages/_receive_upload.slim new file mode 100644 index 0000000..044fcad --- /dev/null +++ b/app/views/packages/_receive_upload.slim @@ -0,0 +1,22 @@ +/ Render this callout on top of the package's details page +/ if images have just been uploaded. +- if (@valid_images and @valid_images.any?) or (@invalid_images and @invalid_images.any?) + .row + .callout.warning + + - if @invalid_images.any? + = fa_icon 'exclamation-circle 2x pull-left' + p These images were not accepted: + + ul + - @invalid_images.each do |image| + li + ' #{image.image_file_name} (#{image.errors[:image].join(' and the image ')}) + + + + - if @valid_images.any? + = fa_icon 'check-circle 2x pull-left' + p + ' #{pluralize(@valid_images.length, 'screenshot')} received successfully. + ' Please review the images and add a description for each image. Thanks. diff --git a/app/views/packages/_user_dropdown.slim b/app/views/packages/_user_dropdown.slim index 642e7c3..baf2003 100644 --- a/app/views/packages/_user_dropdown.slim +++ b/app/views/packages/_user_dropdown.slim @@ -1,13 +1,17 @@ -// TODO: Can be deleted? -// -// Button that reveals a dropdown/modal for users (for their own screenshots) -.text-right - button.small.dropdown.warning.button type="button" data-toggle="admin-info-#{screenshot.id}" - 'Manage your screenshot -.dropdown-pane data-dropdown=true id="admin-info-#{screenshot.id}" - a.button.small.alert[ - href=delete_screenshot_path(screenshot.id) - onclick="return confirm('Really delete the screenshot?');" - ] Delete screenshot - p - i =screenshot.status +/ Ask the user to add a description if not yet done +- if screenshot.description.blank? + / TODO: Allow the user to edit the screenshot's description again? + div + = form_for screenshot, url: update_screenshot_description_path(screenshot) do |f| + label for='description' Please add a short description of this screenshot: + .input-group + span.input-group-label = fa_icon 'edit wobble' + = f.text_field 'description', class: 'input-group-field', maxlength: 80 + .input-group-button + = f.submit 'Save', class: 'button success' + +/ Offer to delete the screenshot +a.button.small.alert[ + href=delete_screenshot_path(screenshot.id) + onclick="return confirm('Really delete your screenshot again?');" + ] Delete your screenshot diff --git a/app/views/packages/details.slim b/app/views/packages/details.slim index 0beb485..e1d3be6 100644 --- a/app/views/packages/details.slim +++ b/app/views/packages/details.slim @@ -1,4 +1,6 @@ -/ = link_to 'Back', :back, class: 'button small' +/ If images were just uploaded then show upload success/errors +/ in a callout. += render 'receive_upload' .row.packagepage .small-12.columns @@ -10,7 +12,7 @@ - if screenshots_visible_to_user(@package).count > 0 - screenshots_visible_to_user(@package).each do |screenshot| .row.listview - .text-center + / .text-center a.black.fancybox href=screenshot.image.url(:large, timestamp: false) rel='fancybox-thumb' title=screenshot.caption = image_tag(screenshot.image.url(:large, timestamp: false), alt: screenshot.caption) - if screenshot.description @@ -26,22 +28,9 @@ - if user_signed_in? and current_user.is_admin? = render(partial: 'admin_dropdown', locals: {screenshot: screenshot}) // or does the screenshot belong to the user (determined by session cookie) - - elsif screenshot.uploaderhash == session[:token] - / = render(partial: 'user_dropdown', locals: {screenshot: screenshot}) - / Ask the user to add a description if not yet done - - if screenshot.description.blank? - .imgcaption - p Please add a brief description of this screenshot: - = form_tag(update_screenshot_description_path(screenshot.package.name, screenshot.id)) - .input-group - = text_field_tag 'description', nil, class: 'input-group-field', maxlength: 80 - .input-group-button - =submit_tag 'Save', class: 'button success' - / Offer to delete the screenshot - a.button.small.alert[ - href=delete_screenshot_path(screenshot.id) - onclick="return confirm('Really delete your screenshot again?');" - ] Delete your screenshot + - elsif user_signed_in? and screenshot.user == current_user + = render(partial: 'user_dropdown', locals: {screenshot: screenshot}) + // or is the user not related to the screenshot and the screenshot is public? // TODO: Move to a seperate page with a form diff --git a/app/views/packages/list.slim b/app/views/packages/list.slim index a65b7bd..5557575 100644 --- a/app/views/packages/list.slim +++ b/app/views/packages/list.slim @@ -26,7 +26,7 @@ = image_tag(screenshot.image.url(:large, timestamp: false), alt: screenshot.caption) - if pkg.screenshots.length > 1 a.black href=package_path(name: pkg.name) - .text-center and #{pluralize(pkg.screenshots.length-1, 'screenshot')} more… + 'and #{pluralize(pkg.screenshots.length-1, 'screenshot')} more… - else a href=package_path(name: pkg.name) img.screenshot src="/images/dummy/no-screenshots-upload-one.svg" diff --git a/app/views/packages/upload.slim b/app/views/packages/upload.slim index 08006dd..3fc9bad 100644 --- a/app/views/packages/upload.slim +++ b/app/views/packages/upload.slim @@ -1,3 +1,5 @@ += render 'receive_upload' + .row h1 = "Upload screenshots for #{@package.name}" diff --git a/app/views/packages/upload_receive.slim b/app/views/packages/upload_receive.slim index 95abdcd..bc9ff64 100644 --- a/app/views/packages/upload_receive.slim +++ b/app/views/packages/upload_receive.slim @@ -1,59 +1,23 @@ -/ This page only gets displayed if there was invalid uploads. -/ Otherwise the user gets redirected to the upload_review page directly. -.row - h1 Some images feel wrong… +h1 Nothing to see here! -.row - p - ' Thanks for your upload. Just wanted to let you know that - ' these images were not accepted: - - ul - - @invalid_images.each do |image| - li - ' #{image.image_file_name} (#{image.errors[:image].join(' and the image ')}) - - - - - if @valid_images.any? - p - ' But do not worry. #{pluralize(@valid_images.length, 'screenshot')} seems good. - ' Let us take a look at them. - - p - a.button href=package_path Okay - -/ -/ = form_for :screenshot, url: upload_review2_path do |f| -/ -/ p 'Please check the images and add -/ ' short one-line descriptions that describe each of the images. -/ -/ - @valid_images.each do |screenshot| -/ +/ / Show upload success/errors and load details view. +/ .row +/ .callout +/ +/ - if @invalid_images.any? +/ p These images were not accepted: +/ +/ ul +/ - @invalid_images.each do |image| +/ li +/ ' #{image.image_file_name} (#{image.errors[:image].join(' and the image ')}) +/ +/ +/ +/ - if @valid_images.any? / .row -/ -/ .small-4.columns -/ a.black.fancybox href=screenshot.image.url(:large, timestamp: false) title='Uploaded image' -/ = image_tag(screenshot.image.url(:large, timestamp: false), alt: 'Uploaded image') -/ .small-8.columns -/ em Your file name: -/ p -/ = screenshot.image_file_name -/ -/ em Description: -/ p -/ = text_field_tag "description-#{screenshot.id}" -/ -/ em Delete this image? -/ p -/ = check_box "delete-#{screenshot.id}", "Delete this image" -/ -/ .row -/ button.button type="submit" id="file-submit" -/ = fa_stacked_icon "check", base: "circle-thin" -/ ' Done -/ -/ - else -/ .row -/ a.button href=upload_path Back to upload form +/ p +/ ' #{pluralize(@valid_images.length, 'screenshot')} received successfully. +/ ' Please review the images and add a description for each image. Thanks. +/ +/ = render 'packages/details.slim' diff --git a/app/views/welcome/home.slim b/app/views/welcome/home.slim index 8903070..377885d 100644 --- a/app/views/welcome/home.slim +++ b/app/views/welcome/home.slim @@ -23,7 +23,6 @@ input.button.postfix type="submit" value="Search" div.row - hr div.large-4.medium-12.small-12.columns.text-center h2 Newest upload a.black href=package_path(@newest_upload.package.name) @@ -47,7 +46,7 @@ div.row a href=upload_path(@most_wanted_package.name) img src="/images/dummy/no-screenshots-upload-one.svg" width="160" p - ' We lack a screenshot for the + ' We lack a screenshot for the strong =>@most_wanted_package.name ' package. Can you a href=package_path(@most_wanted_package.name) provide one diff --git a/config/routes.rb b/config/routes.rb index 34ae0ef..6f05df9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,7 +27,7 @@ Rails.application.routes.draw do post 'upload/:name' => 'packages#upload_receive', as: :upload_receive, name: /[^\/]+/ # TODO: "get" is probably the wrong method to delete a screenshot get 'delete_screenshot/:id' => 'packages#delete_screenshot', as: :delete_screenshot - post 'update_screenshot_description/:name/:id' => 'packages#update_screenshot_description', as: :update_screenshot_description + patch 'update_screenshot_description/:id' => 'packages#update_screenshot_description', as: :update_screenshot_description #post 'report_screenshot/:id' => 'packages#report_screenshot', as: :report_screenshot # TODO: "get" is probably the wrong method to delete a screenshot get 'approve_screenshot/:id' => 'packages#approve_screenshot', as: :approve_screenshot