Merge branch 'master' of git.workaround.org:chaas/debshots

This commit is contained in:
Christoph Haas 2018-05-22 23:14:20 +02:00
commit 181ea83540
29 changed files with 678 additions and 386 deletions

View file

@ -1,37 +0,0 @@
FROM debian:8
MAINTAINER email@christoph-haas.de
# Install apt based dependencies required to run Rails as
# well as RubyGems. As the Ruby image itself is based on a
# Debian image, we use apt-get to install those.
RUN apt-get update && apt-get install -y \
build-essential ruby
RUN apt-get install -y git
RUN apt-get install -y rbenv
# Configure the main working directory. This is the base
# directory used in any further RUN, COPY, and ENTRYPOINT
# commands.
RUN mkdir -p /app
WORKDIR /app
# Copy the Gemfile as well as the Gemfile.lock and install
# the RubyGems. This is a separate step so the dependencies
# will be cached unless changes to one of those two files
# are made.
COPY Gemfile Gemfile.lock ./
RUN gem install bundler && bundle install --jobs 20 --retry 5
# Copy the main application.
COPY . ./
# Expose port 3000 to the Docker host, so we can access it
# from the outside.
EXPOSE 3000
# The main command to run when the container starts. Also
# tell the Rails dev server to bind to all interfaces by
# default.
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

View file

@ -17,5 +17,3 @@ Install the dependencies:
Install a PostgreSQL server: Install a PostgreSQL server:
apt install postgresql apt install postgresql
C

View file

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

View file

@ -21,25 +21,44 @@
// 16. Drilldown // 16. Drilldown
// 17. Dropdown // 17. Dropdown
// 18. Dropdown Menu // 18. Dropdown Menu
// 19. Forms // 19. Flexbox Utilities
// 20. Label // 20. Forms
// 21. Media Object // 21. Label
// 22. Menu // 22. Media Object
// 23. Meter // 23. Menu
// 24. Off-canvas // 24. Meter
// 25. Orbit // 25. Off-canvas
// 26. Pagination // 26. Orbit
// 27. Progress Bar // 27. Pagination
// 28. Responsive Embed // 28. Progress Bar
// 29. Reveal // 29. Prototype Arrow
// 30. Slider // 30. Prototype Border-Box
// 31. Switch // 31. Prototype Border-None
// 32. Table // 32. Prototype Bordered
// 33. Tabs // 33. Prototype Display
// 34. Thumbnail // 34. Prototype Font-Styling
// 35. Title Bar // 35. Prototype List-Style-Type
// 36. Tooltip // 36. Prototype Overflow
// 37. Top Bar // 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'; @import 'util/util';
@ -67,11 +86,16 @@ $body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;
$body-antialiased: true; $body-antialiased: true;
$global-margin: 1rem; $global-margin: 1rem;
$global-padding: 1rem; $global-padding: 1rem;
$global-position: 1rem;
$global-weight-normal: normal; $global-weight-normal: normal;
$global-weight-bold: bold; $global-weight-bold: bold;
$global-radius: 0; $global-radius: 0;
$global-menu-padding: 0 1rem;
$global-menu-nested-margin: 1rem;
$global-text-direction: ltr; $global-text-direction: ltr;
$global-flexbox: false; $global-flexbox: true;
$global-prototype-breakpoints: false;
$global-color-pick-contrast-tolerance: 0;
$print-transparent-backgrounds: true; $print-transparent-backgrounds: true;
@include add-foundation-colors; @include add-foundation-colors;
@ -99,6 +123,7 @@ $grid-column-gutter: (
medium: 30px, medium: 30px,
); );
$grid-column-align-edge: true; $grid-column-align-edge: true;
$grid-column-alias: 'columns';
$block-grid-max: 8; $block-grid-max: 8;
// 4. Base Typography // 4. Base Typography
@ -210,8 +235,17 @@ $accordion-content-padding: 1rem;
// 8. Accordion Menu // 8. Accordion Menu
// ----------------- // -----------------
$accordionmenu-padding: $global-menu-padding;
$accordionmenu-nested-margin: $global-menu-nested-margin;
$accordionmenu-submenu-padding: $accordionmenu-padding;
$accordionmenu-arrows: true; $accordionmenu-arrows: true;
$accordionmenu-arrow-color: $primary-color; $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; $accordionmenu-arrow-size: 6px;
// 9. Badge // 9. Badge
@ -235,11 +269,15 @@ $breadcrumbs-item-color-current: $black;
$breadcrumbs-item-color-disabled: $medium-gray; $breadcrumbs-item-color-disabled: $medium-gray;
$breadcrumbs-item-margin: 0.75rem; $breadcrumbs-item-margin: 0.75rem;
$breadcrumbs-item-uppercase: true; $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 // 11. Button
// ---------- // ----------
$button-font-family: inherit;
$button-padding: 0.85em 1em; $button-padding: 0.85em 1em;
$button-margin: 0 0 $global-margin 0; $button-margin: 0 0 $global-margin 0;
$button-fill: solid; $button-fill: solid;
@ -248,6 +286,7 @@ $button-background-hover: scale-color($button-background, $lightness: -15%);
$button-color: $white; $button-color: $white;
$button-color-alt: $black; $button-color-alt: $black;
$button-radius: $global-radius; $button-radius: $global-radius;
$button-hollow-border-width: 1px;
$button-sizes: ( $button-sizes: (
tiny: 0.6rem, tiny: 0.6rem,
small: 0.75rem, small: 0.75rem,
@ -292,7 +331,7 @@ $card-border: 1px solid $light-gray;
$card-shadow: none; $card-shadow: none;
$card-border-radius: $global-radius; $card-border-radius: $global-radius;
$card-padding: $global-padding; $card-padding: $global-padding;
$card-margin: $global-margin; $card-margin-bottom: $global-margin;
// 15. Close Button // 15. Close Button
// ---------------- // ----------------
@ -319,9 +358,13 @@ $closebutton-color-hover: $black;
$drilldown-transition: transform 0.15s linear; $drilldown-transition: transform 0.15s linear;
$drilldown-arrows: true; $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-color: $primary-color;
$drilldown-arrow-size: 6px; $drilldown-arrow-size: 6px;
$drilldown-background: $white;
// 17. Dropdown // 17. Dropdown
// ------------ // ------------
@ -344,11 +387,24 @@ $dropdown-sizes: (
$dropdownmenu-arrows: true; $dropdownmenu-arrows: true;
$dropdownmenu-arrow-color: $anchor-color; $dropdownmenu-arrow-color: $anchor-color;
$dropdownmenu-arrow-size: 6px; $dropdownmenu-arrow-size: 6px;
$dropdownmenu-arrow-padding: 1.5rem;
$dropdownmenu-min-width: 200px; $dropdownmenu-min-width: 200px;
$dropdownmenu-background: $white; $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; $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; $fieldset-border: 1px solid $medium-gray;
@ -375,11 +431,13 @@ $input-placeholder-color: $medium-gray;
$input-font-family: inherit; $input-font-family: inherit;
$input-font-size: rem-calc(16); $input-font-size: rem-calc(16);
$input-font-weight: $global-weight-normal; $input-font-weight: $global-weight-normal;
$input-line-height: $global-lineheight;
$input-background: $white; $input-background: $white;
$input-background-focus: $white; $input-background-focus: $white;
$input-background-disabled: $light-gray; $input-background-disabled: $light-gray;
$input-border: 1px solid $medium-gray; $input-border: 1px solid $medium-gray;
$input-border-focus: 1px solid $dark-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: inset 0 1px 2px rgba($black, 0.1);
$input-shadow-focus: 0 0 5px $medium-gray; $input-shadow-focus: 0 0 5px $medium-gray;
$input-cursor-disabled: not-allowed; $input-cursor-disabled: not-allowed;
@ -388,7 +446,7 @@ $input-number-spinners: true;
$input-radius: $global-radius; $input-radius: $global-radius;
$form-button-radius: $global-radius; $form-button-radius: $global-radius;
// 20. Label // 21. Label
// --------- // ---------
$label-background: $primary-color; $label-background: $primary-color;
@ -399,26 +457,28 @@ $label-font-size: 0.8rem;
$label-padding: 0.33333rem 0.5rem; $label-padding: 0.33333rem 0.5rem;
$label-radius: $global-radius; $label-radius: $global-radius;
// 21. Media Object // 22. Media Object
// ---------------- // ----------------
$mediaobject-margin-bottom: $global-margin; $mediaobject-margin-bottom: $global-margin;
$mediaobject-section-padding: $global-padding; $mediaobject-section-padding: $global-padding;
$mediaobject-image-width-stacked: 100%; $mediaobject-image-width-stacked: 100%;
// 22. Menu // 23. Menu
// -------- // --------
$menu-margin: 0; $menu-margin: 0;
$menu-margin-nested: 1rem; $menu-nested-margin: $global-menu-nested-margin;
$menu-item-padding: 0.7rem 1rem; $menu-items-padding: $global-menu-padding;
$menu-simple-margin: 1rem;
$menu-item-color-active: $white; $menu-item-color-active: $white;
$menu-item-background-active: get-color(primary); $menu-item-background-active: get-color(primary);
$menu-icon-spacing: 0.25rem; $menu-icon-spacing: 0.25rem;
$menu-item-background-hover: $light-gray; $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; $meter-height: 1rem;
@ -428,23 +488,26 @@ $meter-fill-good: $success-color;
$meter-fill-medium: $warning-color; $meter-fill-medium: $warning-color;
$meter-fill-bad: $alert-color; $meter-fill-bad: $alert-color;
// 24. Off-canvas // 25. Off-canvas
// -------------- // --------------
$offcanvas-size: 250px; $offcanvas-size: 250px;
$offcanvas-vertical-size: 250px; $offcanvas-vertical-size: 250px;
$offcanvas-background: $light-gray; $offcanvas-background: $light-gray;
$offcanvas-shadow: 0 0 10px rgba($black, 0.7); $offcanvas-shadow: 0 0 10px rgba($black, 0.7);
$offcanvas-push-zindex: 1; $offcanvas-inner-shadow-size: 20px;
$offcanvas-overlap-zindex: 10; $offcanvas-inner-shadow-color: rgba($black, 0.25);
$offcanvas-reveal-zindex: 1; $offcanvas-overlay-zindex: 11;
$offcanvas-push-zindex: 12;
$offcanvas-overlap-zindex: 13;
$offcanvas-reveal-zindex: 12;
$offcanvas-transition-length: 0.5s; $offcanvas-transition-length: 0.5s;
$offcanvas-transition-timing: ease; $offcanvas-transition-timing: ease;
$offcanvas-fixed-reveal: true; $offcanvas-fixed-reveal: true;
$offcanvas-exit-background: rgba($white, 0.25); $offcanvas-exit-background: rgba($white, 0.25);
$maincontent-class: 'off-canvas-content'; $maincontent-class: 'off-canvas-content';
// 25. Orbit // 26. Orbit
// --------- // ---------
$orbit-bullet-background: $medium-gray; $orbit-bullet-background: $medium-gray;
@ -459,7 +522,7 @@ $orbit-control-background-hover: rgba($black, 0.5);
$orbit-control-padding: 1rem; $orbit-control-padding: 1rem;
$orbit-control-zindex: 10; $orbit-control-zindex: 10;
// 26. Pagination // 27. Pagination
// -------------- // --------------
$pagination-font-size: rem-calc(14); $pagination-font-size: rem-calc(14);
@ -477,7 +540,7 @@ $pagination-mobile-items: false;
$pagination-mobile-current-item: false; $pagination-mobile-current-item: false;
$pagination-arrows: true; $pagination-arrows: true;
// 27. Progress Bar // 28. Progress Bar
// ---------------- // ----------------
$progress-height: 1rem; $progress-height: 1rem;
@ -486,7 +549,168 @@ $progress-margin-bottom: $global-margin;
$progress-meter-background: $primary-color; $progress-meter-background: $primary-color;
$progress-radius: $global-radius; $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); $responsive-embed-margin-bottom: rem-calc(16);
@ -495,7 +719,7 @@ $responsive-embed-ratios: (
widescreen: 16 by 9, widescreen: 16 by 9,
); );
// 29. Reveal // 47. Reveal
// ---------- // ----------
$reveal-background: $white; $reveal-background: $white;
@ -507,7 +731,7 @@ $reveal-radius: $global-radius;
$reveal-zindex: 1005; $reveal-zindex: 1005;
$reveal-overlay-background: rgba($black, 0.45); $reveal-overlay-background: rgba($black, 0.45);
// 30. Slider // 48. Slider
// ---------- // ----------
$slider-width-vertical: 0.5rem; $slider-width-vertical: 0.5rem;
@ -521,7 +745,7 @@ $slider-handle-background: $primary-color;
$slider-opacity-disabled: 0.25; $slider-opacity-disabled: 0.25;
$slider-radius: $global-radius; $slider-radius: $global-radius;
// 31. Switch // 49. Switch
// ---------- // ----------
$switch-background: $medium-gray; $switch-background: $medium-gray;
@ -537,7 +761,7 @@ $switch-paddle-offset: 0.25rem;
$switch-paddle-radius: $global-radius; $switch-paddle-radius: $global-radius;
$switch-paddle-transition: all 0.25s ease-out; $switch-paddle-transition: all 0.25s ease-out;
// 32. Table // 50. Table
// --------- // ---------
$table-background: $white; $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-head-font-color: $body-font-color;
$table-foot-font-color: $body-font-color; $table-foot-font-color: $body-font-color;
$show-header-for-stacked: false; $show-header-for-stacked: false;
$table-stack-breakpoint: medium;
// 33. Tabs // 51. Tabs
// -------- // --------
$tab-margin: 0; $tab-margin: 0;
@ -575,7 +800,7 @@ $tab-content-border: $light-gray;
$tab-content-color: $body-font-color; $tab-content-color: $body-font-color;
$tab-content-padding: 1rem; $tab-content-padding: 1rem;
// 34. Thumbnail // 52. Thumbnail
// ------------- // -------------
$thumbnail-border: solid 4px $white; $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-transition: box-shadow 200ms ease-out;
$thumbnail-radius: $global-radius; $thumbnail-radius: $global-radius;
// 35. Title Bar // 53. Title Bar
// ------------- // -------------
$titlebar-background: $black; $titlebar-background: $black;
@ -596,20 +821,22 @@ $titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray; $titlebar-icon-color-hover: $medium-gray;
$titlebar-icon-spacing: 0.25rem; $titlebar-icon-spacing: 0.25rem;
// 36. Tooltip // 54. Tooltip
// ----------- // -----------
$has-tip-cursor: help;
$has-tip-font-weight: $global-weight-bold; $has-tip-font-weight: $global-weight-bold;
$has-tip-border-bottom: dotted 1px $dark-gray; $has-tip-border-bottom: dotted 1px $dark-gray;
$tooltip-background-color: $black; $tooltip-background-color: $black;
$tooltip-color: $white; $tooltip-color: $white;
$tooltip-padding: 0.75rem; $tooltip-padding: 0.75rem;
$tooltip-max-width: 10rem;
$tooltip-font-size: $small-font-size; $tooltip-font-size: $small-font-size;
$tooltip-pip-width: 0.75rem; $tooltip-pip-width: 0.75rem;
$tooltip-pip-height: $tooltip-pip-width * 0.866; $tooltip-pip-height: $tooltip-pip-width * 0.866;
$tooltip-radius: $global-radius; $tooltip-radius: $global-radius;
// 37. Top Bar // 55. Top Bar
// ----------- // -----------
$topbar-padding: 0.5rem; $topbar-padding: 0.5rem;
@ -618,3 +845,18 @@ $topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 0.5rem 1rem 0.5rem 0; $topbar-title-spacing: 0.5rem 1rem 0.5rem 0;
$topbar-input-width: 200px; $topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium; $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 foundation_and_overrides
*= require cookies_eu *= require cookies_eu
*= require fancybox *= require fancybox
* Flash/notifications using http://www.amaranjs.com/
* https://github.com/hakanersu/AmaranJS
*= require amaran
*= require font-awesome *= require font-awesome
*/ */

View file

@ -370,3 +370,26 @@ a.black
// font-weight: bold; // font-weight: bold;
line-height: 1; 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,13 +7,15 @@ class ApplicationController < ActionController::Base
# filter_parameter_logging :password # filter_parameter_logging :password
before_action :get_current_users_screenshots, :debian_sso 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. # Query for packages that were uploaded by the current user.
# As AAA is not yet implemented it means looking for uploads # If the user is anonymous then find the uploads by the cookie token.
# that correspond to the user's cookie token. # If the user is logged in then find the uploads by matching the user id.
def get_current_users_screenshots def get_current_users_screenshots
if session[:token] if user_signed_in?
@current_users_screenshots = Screenshot.uploaded_by(session[:token]) @current_users_screenshots = current_user.screenshots
end end
end end
@ -50,4 +52,11 @@ class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource) def after_sign_in_path_for(resource)
my_welcome_path my_welcome_path
end end
# better_errors gets extremely slow without this hack
# see: https://github.com/charliesome/better_errors/issues/341
def better_errors_hack
env['puma.config'].options.user_options.delete :app
end
end end

View file

@ -1,62 +1,96 @@
class PackagesController < ApplicationController class PackagesController < ApplicationController
protect_from_forgery :except => :legacy_uploadfile protect_from_forgery :except => :legacy_uploadfile
def list def list
@packages = query_packages.paginate(page: params[:page], per_page: 6) @packages = query_packages.paginate(page: params[:page], per_page: 6)
end end
def grid def grid
@packages = query_packages.paginate(page: params[:page], per_page: 24) @packages = query_packages.paginate(page: params[:page], per_page: 24)
end end
def details def details
# TODO: Get only screenshots visible to the user (admin or owner or approved)
@package = Package.find_by(name: params[:name]) @package = Package.find_by(name: params[:name])
unless @package unless @package
@packagename = params[:name] @packagename = params[:name]
render 'notfound' render 'notfound'
end end
end end
# Show upload form for new images # Show upload form for new images
def upload def upload
@package = Package.find_by!(name: params[:name]) @package = Package.find_by!(name: params[:name])
end end
# POST target of the screenshots upload form. # POST target of the screenshots upload form.
# Receives uploaded images. Checks if they are valid. Asks for description. # Receives uploaded images. Checks if they are valid. Asks for description.
# This action saves the screenshots already if they are valid. The user is
# then given the chance to comment on and delete the screenshots again.
def upload_receive def upload_receive
# Send a cookie to remember the user by the cookie session. # Create a pseudo user account for the user.
create_user_token # 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]) @package = Package.find_by!(name: params[:name])
@valid_images = [] @valid_images = []
@invalid_images = [] @invalid_images = []
params[:file].each do |img| params[:file].each do |img|
new_screenshot = @package.screenshots.new(image: img) new_screenshot = @package.screenshots.new(image: img)
# Check if the image was valid # Check if the image was valid
if new_screenshot.valid? if new_screenshot.valid?
new_screenshot.uploaderhash = session[:token] new_screenshot.uploaderhash = session[:token]
new_screenshot.uploaderip = session[:ip] new_screenshot.uploaderip = session[:ip]
new_screenshot.version = @package.version new_screenshot.version = @package.version
new_screenshot.user = current_user
# TODO: must it be moderated first?
new_screenshot.save new_screenshot.save
Log.log "Screenshot #{new_screenshot.id} uploaded successfully from #{session[:ip]}. User has token #{session[:token]}" 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 @valid_images.push new_screenshot
else else
Log.log "Screenshot #{new_screenshot.image_file_name} invalid (#{new_screenshot.errors[:image]})."
@invalid_images.push new_screenshot @invalid_images.push new_screenshot
end end
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
# TODO # TODO
# if @invalid_images… # if @invalid_images…
# ' #{image.image_file_name} (#{image.errors[:image].join(' and ')}) # ' #{image.image_file_name} (#{image.errors[:image].join(' and ')})
# redirect_to package_path redirect_to package_path
# render :details
end end
# Legacy action to upload an image along with metadata. # Legacy action to upload an image along with metadata.
# This was used in Debshots 1.x as the default upload method. # This was used in Debshots 1.x as the default upload method.
# This method allows that old-style way to upload screenshots. # This method allows that old-style way to upload screenshots.
@ -69,9 +103,9 @@ class PackagesController < ApplicationController
# - file # - file
def legacy_uploadfile def legacy_uploadfile
@package = Package.find_by!(name: params[:packagename]) @package = Package.find_by!(name: params[:packagename])
new_screenshot = @package.screenshots.new(image: params[:file]) new_screenshot = @package.screenshots.new(image: params[:file])
# Check if the image was valid # Check if the image was valid
if new_screenshot.valid? if new_screenshot.valid?
# new_screenshot.uploaderhash = session[:token] # new_screenshot.uploaderhash = session[:token]
@ -85,27 +119,30 @@ class PackagesController < ApplicationController
head :not_acceptable head :not_acceptable
end end
end end
def delete_screenshot def delete_screenshot
# Is the user allowed to delete the screenshot? # Is the user allowed to delete the screenshot?
@screenshot = Screenshot.find(params[:id]) @screenshot = Screenshot.find(params[:id])
if user_can_alter_screenshot? # 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 @screenshot.destroy
flash['notice'] = "Screenshot deleted." flash['notice'] = "Screenshot deleted."
redirect_to :back redirect_back(fallback_location: package_path(name: @screenshot.package.name))
else else
head :forbidden head :forbidden
end end
end end
def approve_screenshot def approve_screenshot
@screenshot = Screenshot.find(params[:id]) @screenshot = Screenshot.find(params[:id])
@screenshot.approve_screenshot! @screenshot.approve_screenshot!
flash['notice'] = "Screenshot approved." flash['notice'] = "Screenshot approved."
redirect_to :back redirect_back(fallback_location: package_path)
end end
# Returns a 160x120 thumbnail image if posssible. # 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 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 # If the package is found but has no screenshots then it also returns a
@ -116,25 +153,25 @@ class PackagesController < ApplicationController
thumbnail404 thumbnail404
return return
end end
# Called as /thumbnail-with-version/:name/:version # Called as /thumbnail-with-version/:name/:version
if params[:version] if params[:version]
@screenshot = @package.best_screenshot_for_version(params[:version]) @screenshot = @package.best_screenshot_for_version(params[:version])
# Called as /thumbnail/:name # Called as /thumbnail/:name
else else
@screenshot = @package.screenshots.first @screenshot = @package.screenshots.first
end end
# Return a 404 if the package has no screenshots or the image was not found # Return a 404 if the package has no screenshots or the image was not found
unless @screenshot and @screenshot.image.path unless @screenshot and @screenshot.image.path
thumbnail404 thumbnail404
return return
end end
# Send the thumbnail (uses X-Sendfile or similar if possible) # Send the thumbnail (uses X-Sendfile or similar if possible)
send_file @screenshot.image.path(:thumb), type: "image/png", disposition: 'inline' send_file @screenshot.image.path(:thumb), type: "image/png", disposition: 'inline'
end end
# Returns a large screenshot image if posssible. # 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 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 # If the package is found but has no screenshots then it also returns a
@ -145,39 +182,41 @@ class PackagesController < ApplicationController
screenshot404 screenshot404
return return
end end
# Called as /thumbnail-with-version/:name/:version # Called as /thumbnail-with-version/:name/:version
if params[:version] if params[:version]
@screenshot = @package.best_screenshot_for_version(params[:version]) @screenshot = @package.best_screenshot_for_version(params[:version])
# Called as /thumbnail/:name # Called as /thumbnail/:name
else else
@screenshot = @package.screenshots.first @screenshot = @package.screenshots.first
end end
# Return a 404 if the package has no screenshots or the image was not found # Return a 404 if the package has no screenshots or the image was not found
unless @screenshot and @screenshot.image.path unless @screenshot and @screenshot.image.path
screenshot404 screenshot404
return return
end end
# Send the thumbnail (uses X-Sendfile or similar if possible) # Send the thumbnail (uses X-Sendfile or similar if possible)
send_file @screenshot.image.path(:large), type: "image/png", disposition: 'inline' send_file @screenshot.image.path(:large), type: "image/png", disposition: 'inline'
end end
# Receives a form with a simple text field 'description' so that users can update # Receives a form with a simple text field 'description' so that users can update
# the description of their screenshot. # the description of their screenshot.
def update_screenshot_description def update_screenshot_description
@screenshot = Screenshot.find(params[:id]) # TODO: Check permissions to do that
@screenshot.description = params[:description] @screenshot = Screenshot.find(params[:id])
@screenshot.save! # @screenshot.description = params[:description]
flash['notice'] = "Description updated." @screenshot.update params_screenshot_description
redirect_to :back @screenshot.save!
end flash['notice'] = "Description updated."
redirect_back(fallback_location: package_path(name: @screenshot.package.name))
# Receive an anonymous report from a user to have a screenshot removed. end
def report_screenshot
@screenshot = Screenshot.find(params[:id]) # Receive an anonymous report from a user to have a screenshot removed.
# if verify_recaptcha def report_screenshot
@screenshot = Screenshot.find(params[:id])
# if verify_recaptcha
@screenshot.delete_reason = params[:delete_reason] @screenshot.delete_reason = params[:delete_reason]
@screenshot.markedfordelete = true @screenshot.markedfordelete = true
if @screenshot.valid? if @screenshot.valid?
@ -187,46 +226,57 @@ class PackagesController < ApplicationController
errors = @screenshot.errors.to_a.join(' and ') errors = @screenshot.errors.to_a.join(' and ')
flash['alert'] = "Sorry. #{errors}" flash['alert'] = "Sorry. #{errors}"
end end
# end # end
redirect_to :back 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
# 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 end
case params[:show] # 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])
end
case params[:show]
when 'with' when 'with'
packages = packages.with_screenshots packages = packages.with_screenshots
logger.debug 'Limiting packages to those with screenshots' logger.debug 'Limiting packages to those with screenshots'
@ -234,31 +284,23 @@ class PackagesController < ApplicationController
packages = packages.without_screenshots packages = packages.without_screenshots
logger.debug 'Limiting packages to those without screenshots' logger.debug 'Limiting packages to those without screenshots'
end end
return packages return packages
end end
# Check if the user is allowed to do changed to a screenshot
def user_can_alter_screenshot?
# - Is this the user's own screenshot?
# - Is the user an admin (=logged in)?
@screenshot.uploaderhash == session[:token] or user_signed_in?
end
# Store a random identifier and the client's IP address in the session # Store a random identifier and the client's IP address in the session
# for later identification. # for later identification.
def create_user_token def create_user_token
session[:token] ||= SecureRandom.hex session[:token] ||= SecureRandom.hex
session[:ip] ||= request.remote_ip session[:ip] ||= request.remote_ip
end end
# Get reviews of this package from the Ubuntu API # Get reviews of this package from the Ubuntu API
def get_ubuntu_reviews(packagename) def get_ubuntu_reviews(packagename)
# Use the URL defined in the configuration to get a JSON string # Use the URL defined in the configuration to get a JSON string
url = Rails.configuration.ubuntu_reviews_api_url % packagename url = Rails.configuration.ubuntu_reviews_api_url % packagename
logger.debug "Loading Ubuntu reviews for package #{packagename} from #{url}" logger.debug "Loading Ubuntu reviews for package #{packagename} from #{url}"
body = open(url).read body = open(url).read
# Turn JSON into a Ruby data structure # Turn JSON into a Ruby data structure
json = JSON.parse(body) json = JSON.parse(body)
@ -269,5 +311,8 @@ class PackagesController < ApplicationController
json = json.sort { |x,y| y['usefulness_total'].to_i <=> x['usefulness_total'].to_i} json = json.sort { |x,y| y['usefulness_total'].to_i <=> x['usefulness_total'].to_i}
return json return json
end end
def params_screenshot_description
params.require(:screenshot).permit(:description)
end
end end

View file

@ -3,16 +3,16 @@ module PackagesHelper
# Return a query of all screenshots that the current user may see # Return a query of all screenshots that the current user may see
# Consists of: # Consists of:
# - approved (public) screenshots # - approved (public) screenshots
# - screenshots uploaded by the user (determined by cookie session) # - screenshots uploaded by the user
# - all screenshots if the user is logged in # - all screenshots if the user is an admin
def screenshots_visible_to_user(package) def screenshots_visible_to_user(package)
if user_signed_in? if user_signed_in? and current_user.is_admin?
# User is an admin # User is an admin
package.screenshots.order('created_at DESC') package.screenshots
elsif user_signed_in?
current_user.screenshots
else else
package.screenshots.where( package.screenshots.where(approved: true).order('created_at DESC')
"approved=true OR uploaderhash=?", session[:token]
).order('created_at DESC')
end end
end end

View file

@ -27,5 +27,6 @@ class Log < ApplicationRecord
logger.error "Could not log to database - validation errors: #{log.errors.to_a}" unless log.valid? logger.error "Could not log to database - validation errors: #{log.errors.to_a}" unless log.valid?
log.save log.save
# log # log
logger.info "Log.log - #{log.section} - #{log.message}"
end end
end end

View file

@ -48,7 +48,7 @@ class Package < ApplicationRecord
# Return a query of all approved/public screenshots of this package # Return a query of all approved/public screenshots of this package
def screenshots_approved def screenshots_approved
self.screenshots.find_by(approved: true) self.screenshots.where(approved: true)
end end
# Return a query of all approved/public screenshots of this package # Return a query of all approved/public screenshots of this package

View file

@ -90,33 +90,8 @@ class Screenshot < ApplicationRecord
end end
end end
# Check if the user is allowed to change this screenshot
def user_can_alter?
# - Is this the user's own screenshot? (anonymous)
if self.uploaderhash == session[:token]
logger.debug "Screenshot belongs to current anonymous user"
return true
end
# - Is this the user's own screenshot? (anonymous)
if self.user = current_user
logger.debug "Screenshot belongs to user who is currently logged in"
return true
end
# - Is the user an admin (=logged in)?
if current_user.can_admin?
logger.debug "User is an administrator and has super powers"
return true
end
return false
end
# Publish a screenshot from the moderation queue # Publish a screenshot from the moderation queue
def approve_screenshot! def approve_screenshot!
self.delete_reason = nil self.delete_reason = nil
self.markedfordelete = false self.markedfordelete = false
self.approved = true self.approved = true
@ -131,11 +106,6 @@ class Screenshot < ApplicationRecord
self.find_by(approved: true) self.find_by(approved: true)
end end
# Query for screenshots being uploaded by a certain user (by their token)
def self.uploaded_by(token)
self.where(approved: false, uploaderhash: token)
end
# Check whether the user has administrative permissions # Check whether the user has administrative permissions
def can_admin? def can_admin?
self.admin == 1 self.admin == 1

View file

@ -32,6 +32,8 @@ class User < ApplicationRecord
'Amazon' 'Amazon'
when 'github' when 'github'
'GitHub' 'GitHub'
else
'local authentication'
end end
end end

View file

@ -3,24 +3,18 @@
- if flash - if flash
- flash.each do |name,msg| - flash.each do |name,msg|
- case name - case name
- when 'notice' - when 'timedout'
- flash_color = 'white' / Workaround for Devise sometimes sending "true"
- flash_bgcolor = 'green' - next
- when 'alert' - when 'notice'
- flash_color = 'white' .callout.warning data-closable=true
- flash_bgcolor = 'red' = fa_icon 'info-circle wobble 2x'
- else strong< = msg
- flash_color = 'black' - when 'error'
- flash_bgcolor = 'gray' .callout.alert data-closable=true
= fa_icon 'meh-o wobble 2x'
javascript: strong< = msg
$.amaran({ - else
'content': { .callout.primary data-closable=true
'message' :'#{msg}', = fa_icon 'info-circle wobble 2x'
'color' : '#{flash_color}', strong< = msg
'bgcolor' : '#{flash_bgcolor}'
},
'position' :'bottom right',
'theme' : 'colorful',
'delay' : 7000
});

View file

@ -1,14 +1,14 @@
nav.top-bar .top-bar
.row .top-bar-left
.top-bar-left ul.menu
.top-bar-title li.menu-text
a href='/' a href='/'
// TODO: Make the logo depend on the virtual host // TODO: Make the logo depend on the virtual host
img src="/images/logos/debian.svg" width="35" alt="Debian logo" img src="/images/logos/debian.svg" width="35" alt="Debian logo"
// TODO: Use proper distribution name // TODO: Use proper distribution name
' Debian Screenshots > ' Debian Screenshots >
.top-bar-right .top-bar-right
ul.dropdown.menu data-dropdown-menu=true ul.dropdown.menu data-dropdown-menu=true
li class=('active' if controller_name=='welcome' and action_name=='home') li class=('active' if controller_name=='welcome' and action_name=='home')
a href="/" Home a href="/" Home

View file

@ -17,7 +17,7 @@
.small-6.columns .small-6.columns
p Please moderate: p Please moderate:
- @pending_screenshots.each do |screenshot| - @pending_screenshots.each do |screenshot|
.container.listview.text-center .container.listview
/ = screenshot.status / = screenshot.status
a.black.fancybox href=screenshot.image.url(:large, timestamp: false) rel='fancybox-thumb' title=screenshot.caption 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) = image_tag(screenshot.image.url(:large, timestamp: false), alt: screenshot.caption)

View file

@ -13,14 +13,16 @@
/ TODO: submit - save / TODO: submit - save
h2 Your email address - unless current_user.email.blank?
p = current_user.email h2 Your email address
p.help-text p = current_user.email
' This web site recognizes you by your email address. p.help-text
' Don't worry - it will not be shown or given to anyone. ' 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.provider.blank?
h2 Single-sign-on provider
p You logged in using #{current_user.pretty_provider}.
h2 Number of screenshots you uploaded h2 Number of screenshots you uploaded
p = current_user.screenshots.count p = current_user.screenshots.count

View file

@ -1,35 +1,33 @@
// Button that reveals a dropdown/modal for moderation/reporting // Button that reveals a dropdown/modal for moderation/reporting
.button-bar button-group
ul.button-group - if user_signed_in?
- if not screenshot.approved a.button.small.alert[
li href=delete_screenshot_path(screenshot.id)
a.button.small.success[ onclick="return confirm('Really delete the screenshot?');"
href=approve_screenshot_path(screenshot.id) ] Delete screenshot
method='post'
] Approve screenshot
- elsif screenshot.markedfordelete
li
a.button.small.success[
href=approve_screenshot_path(screenshot.id)
method='post'
] Keep 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 - if not screenshot.approved
p Status: #{screenshot.adminstatus} a.button.small.success[
p Uploader IP=#{screenshot.uploaderip} href=approve_screenshot_path(screenshot.id)
p Uploader Token=#{session[:token]} method='post'
p Uploaded #{screenshot.age_days} (#{screenshot.created_at}) ] 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 / Display additional information to admins
/ / Allow anonymous users to report inappropriate screenshots p Status: #{screenshot.adminstatus}
/ = form_tag(report_screenshot_path(screenshot.id)) p Uploader IP=#{screenshot.uploaderip}
/ = text_area_tag 'delete_reason', nil, class: 'input-group-field', maxlength: 100, rows: 3, cols: 50 p Uploader Token=#{session[:token]}
/ = submit_tag 'Request removal', class: 'button alert' 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

@ -1,7 +1,7 @@
// TODO: Handle packages with multiple screenshots // TODO: Handle packages with multiple screenshots
a.black href=package_path(name: pkg.name) a.black href=package_path(name: pkg.name)
div.grid-thumbnail div.grid-thumbnail
- if pkg.screenshots.any? - if pkg.screenshots_approved.any?
// TODO: smarter selection of the most useful screenshot instead of taking the first one // TODO: smarter selection of the most useful screenshot instead of taking the first one
- screenshot = pkg.screenshots.first - screenshot = pkg.screenshots.first
= image_tag(screenshot.image.url(:thumb, timestamp: false), alt: screenshot.caption, class: 'thumbnail') = image_tag(screenshot.image.url(:thumb, timestamp: false), alt: screenshot.caption, class: 'thumbnail')

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? / Ask the user to add a description if not yet done
// - if screenshot.description.blank?
// Button that reveals a dropdown/modal for users (for their own screenshots) / TODO: Allow the user to edit the screenshot's description again?
.text-right div
button.small.dropdown.warning.button type="button" data-toggle="admin-info-#{screenshot.id}" = form_for screenshot, url: update_screenshot_description_path(screenshot) do |f|
'Manage your screenshot label for='description' Please add a short description of this screenshot:
.dropdown-pane data-dropdown=true id="admin-info-#{screenshot.id}" .input-group
a.button.small.alert[ span.input-group-label = fa_icon 'edit wobble'
href=delete_screenshot_path(screenshot.id) = f.text_field 'description', class: 'input-group-field', maxlength: 80
onclick="return confirm('Really delete the screenshot?');" .input-group-button
] Delete screenshot = f.submit 'Save', class: 'button success'
p
i =screenshot.status / 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 .row.packagepage
.small-12.columns .small-12.columns
@ -10,7 +12,7 @@
- if screenshots_visible_to_user(@package).count > 0 - if screenshots_visible_to_user(@package).count > 0
- screenshots_visible_to_user(@package).each do |screenshot| - screenshots_visible_to_user(@package).each do |screenshot|
.row.listview .row.listview
.text-center / .text-center
a.black.fancybox href=screenshot.image.url(:large, timestamp: false) rel='fancybox-thumb' title=screenshot.caption 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) = image_tag(screenshot.image.url(:large, timestamp: false), alt: screenshot.caption)
- if screenshot.description - if screenshot.description
@ -26,22 +28,9 @@
- if user_signed_in? and current_user.is_admin? - if user_signed_in? and current_user.is_admin?
= render(partial: 'admin_dropdown', locals: {screenshot: screenshot}) = render(partial: 'admin_dropdown', locals: {screenshot: screenshot})
// or does the screenshot belong to the user (determined by session cookie) // or does the screenshot belong to the user (determined by session cookie)
- elsif screenshot.uploaderhash == session[:token] - elsif user_signed_in? and screenshot.user == current_user
/ = render(partial: 'user_dropdown', locals: {screenshot: screenshot}) = 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
// or is the user not related to the screenshot and the screenshot is public? // or is the user not related to the screenshot and the screenshot is public?
// TODO: Move to a seperate page with a form // TODO: Move to a seperate page with a form

View file

@ -19,14 +19,14 @@
.pkgname .pkgname
=pkg.name =pkg.name
' > ' >
- if pkg.screenshots.any? - if pkg.screenshots_approved.any?
// TODO: smarter search for the best screenshot instead of taking the first one // TODO: smarter search for the best screenshot instead of taking the first one
- screenshot = pkg.screenshots.first - screenshot = pkg.screenshots.first
a.black title=screenshot.caption href=package_path(name: pkg.name) a.black title=screenshot.caption href=package_path(name: pkg.name)
= image_tag(screenshot.image.url(:large, timestamp: false), alt: screenshot.caption) = image_tag(screenshot.image.url(:large, timestamp: false), alt: screenshot.caption)
- if pkg.screenshots.length > 1 - if pkg.screenshots.length > 1
a.black href=package_path(name: pkg.name) 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 - else
a href=package_path(name: pkg.name) a href=package_path(name: pkg.name)
img.screenshot src="/images/dummy/no-screenshots-upload-one.svg" img.screenshot src="/images/dummy/no-screenshots-upload-one.svg"

View file

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

View file

@ -0,0 +1,23 @@
h1 Nothing to see here!
/ / 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
/ 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

@ -1,50 +0,0 @@
.row
h1 Your uploaded images
.row
p
' Thanks for your upload. Please check the images and add
' short one-line descriptions that describe each of the images.
- if @invalid_images.any?
.callout.warning
p
' However the following screenshots were not accepted:
ul
- @invalid_images.each do |image|
li
' #{image.image_file_name} (#{image.errors[:image].join(' and ')})
- if @valid_images.any?
= form_for :screenshot, url: upload_review2_path do |f|
- @valid_images.each do |screenshot|
.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

View file

@ -23,7 +23,6 @@
input.button.postfix type="submit" value="Search" input.button.postfix type="submit" value="Search"
div.row div.row
hr
div.large-4.medium-12.small-12.columns.text-center div.large-4.medium-12.small-12.columns.text-center
h2 Newest upload h2 Newest upload
a.black href=package_path(@newest_upload.package.name) a.black href=package_path(@newest_upload.package.name)
@ -47,7 +46,7 @@ div.row
a href=upload_path(@most_wanted_package.name) a href=upload_path(@most_wanted_package.name)
img src="/images/dummy/no-screenshots-upload-one.svg" width="160" img src="/images/dummy/no-screenshots-upload-one.svg" width="160"
p p
' We lack a screenshot for the ' We lack a screenshot for the
strong =>@most_wanted_package.name strong =>@most_wanted_package.name
' package. Can you ' package. Can you
a href=package_path(@most_wanted_package.name) provide one 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: /[^\/]+/ post 'upload/:name' => 'packages#upload_receive', as: :upload_receive, name: /[^\/]+/
# TODO: "get" is probably the wrong method to delete a screenshot # TODO: "get" is probably the wrong method to delete a screenshot
get 'delete_screenshot/:id' => 'packages#delete_screenshot', as: :delete_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 #post 'report_screenshot/:id' => 'packages#report_screenshot', as: :report_screenshot
# TODO: "get" is probably the wrong method to delete a screenshot # TODO: "get" is probably the wrong method to delete a screenshot
get 'approve_screenshot/:id' => 'packages#approve_screenshot', as: :approve_screenshot get 'approve_screenshot/:id' => 'packages#approve_screenshot', as: :approve_screenshot

60
doc/TODO Normal file
View file

@ -0,0 +1,60 @@
Next tasks
> Before next deployment.
FIXED Auto-approve admin uploads
FIXED Why does weboob-qt show an uploaded image in grid mode if no screenshot is approved?
FIXED Redirect back to upload form if all uploads are invalid
FIXED Improve callout design. No javascript. Better readability. At the top.
Main text on details page is not shown
Remove appearances of "token" everywhere.
Hint the user to add missing descriptions.
Fix upload process.
Fix my/my uploads.
Fix admin menu.
Make sure that the moderation workflow works.
Any upload will trigger the creation of an ad-hoc account.
Screenshots will always be assigned to a user.
After an upload ask the user to add descriptions to the
screenshots. And allow them to delete the screenshots.
Check all code with "current_user" and "user_signed_in?" to
make sure it matches the new concept.
Create a user account called "Anonymous" - via a migration.
All existing screenshots will become uploaded by that user.
Turn moderation into a seperate page showing just the one
screenshot and aks the user for a deletion reason.
Make sure that users can delete their own screenshots but
no other.
Fix formatting of http://localhost:3000/packages/list?search=weboob&show=
(Enumerations etc.)
> After next deployment
Make sure that ad-hoc users are recognized when they visit again.
Devise should recognize them by their session cookie.
Offer anonymous users to become real users to track their uploads.
The screenshots will have to be moved to the new account
and the ad-hoc account can be deleted.
Move ad-hoc/anonymous uploads to the main anonymous user after
a week.
Add tests.