Dammit… I can't remember what I changed and why

This commit is contained in:
Christoph Haas 2018-05-06 19:07:41 +02:00
parent 09b0df40f7
commit dd7527691e
20 changed files with 579 additions and 319 deletions

View file

@ -15,7 +15,6 @@
//= require foundation
//= require fancybox
//= require cookies_eu
//= require jquery.amaran
//= require_tree .
$(function(){ $(document).foundation(); });

View file

@ -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;

View file

@ -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
*/

View file

@ -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;
}

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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'

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -1,3 +1,5 @@
= render 'receive_upload'
.row
h1 = "Upload screenshots for #{@package.name}"

View file

@ -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'

View file

@ -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

View file

@ -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