Merge with master

This commit is contained in:
Senén Rodero Rodríguez
2017-09-28 13:36:20 +02:00
183 changed files with 3101 additions and 1935 deletions

View File

@@ -1,6 +1,8 @@
inherit_from: .rubocop_todo.yml
AllCops:
DisplayCopNames: true
DisplayStyleGuide: true
Include:
- '**/Rakefile'
- '**/config.ru'

View File

@@ -283,7 +283,6 @@ Lint/ParenthesesAsGroupedExpression:
# Cop supports --auto-correct.
Lint/StringConversionInInterpolation:
Exclude:
- 'app/models/poll/final_recount.rb'
- 'app/models/poll/null_result.rb'
- 'app/models/poll/partial_result.rb'
- 'app/models/poll/white_result.rb'
@@ -455,12 +454,6 @@ Style/ClassVars:
- 'app/models/organization.rb'
- 'app/models/user.rb'
# Offense count: 6
# Cop supports --auto-correct.
Style/ColonMethodCall:
Exclude:
- 'spec/models/budget/investment_spec.rb'
# Offense count: 12
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions.
@@ -481,12 +474,6 @@ Style/DoubleNegation:
Exclude:
- 'app/models/flag.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/EmptyCaseCondition:
Exclude:
- 'app/models/concerns/verification.rb'
# Offense count: 2
# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
@@ -528,20 +515,6 @@ Style/Lambda:
- 'app/models/vote.rb'
- 'lib/graph_ql/api_types_creator.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/MethodCallWithoutArgsParentheses:
Exclude:
- 'app/controllers/management/document_verifications_controller.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline
Style/MethodDefParentheses:
Exclude:
- 'spec/helpers/comments_helper_spec.rb'
# Offense count: 1
Style/MultilineBlockChain:
Exclude:
@@ -573,25 +546,6 @@ Style/MutableConstant:
- 'lib/tag_sanitizer.rb'
- 'lib/wysiwyg_sanitizer.rb'
# Offense count: 29
# Cop supports --auto-correct.
Style/NestedParenthesizedCalls:
Exclude:
- 'spec/features/debates_spec.rb'
- 'spec/features/emails_spec.rb'
- 'spec/features/valuation/budget_investments_spec.rb'
- 'spec/features/valuation/spending_proposals_spec.rb'
- 'spec/helpers/settings_helper_spec.rb'
- 'spec/helpers/verification_helper_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Exclude:
- 'app/controllers/officing/results_controller.rb'
# Offense count: 54
# Cop supports --auto-correct.
# Configuration parameters: Strict.
@@ -626,15 +580,6 @@ Style/ParallelAssignment:
- 'lib/active_model/dates.rb'
- 'spec/support/common_actions.rb'
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: AllowSafeAssignment.
Style/ParenthesesAroundCondition:
Exclude:
- 'app/controllers/proposals_controller.rb'
- 'app/models/debate.rb'
- 'app/models/proposal.rb'
# Offense count: 11
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
# NamePrefix: is_, has_, have_
@@ -669,11 +614,6 @@ Style/RedundantBegin:
- 'app/controllers/graphql_controller.rb'
- 'app/models/legislation/annotation.rb'
# Offense count: 55
# Cop supports --auto-correct.
Style/RedundantParentheses:
Enabled: false
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
@@ -699,22 +639,6 @@ Style/SafeNavigation:
Exclude:
- 'app/models/signature.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Exclude:
- 'spec/features/budgets/investments_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyleForMultiline, SupportedStylesForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArguments:
Exclude:
- 'app/controllers/legislation/answers_controller.rb'
# Offense count: 9
# Configuration parameters: SupportedStyles.
# SupportedStyles: snake_case, camelCase

View File

@@ -14,6 +14,7 @@
//= require jquery_ujs
//= require jquery-ui/widgets/datepicker
//= require jquery-ui/i18n/datepicker-es
//= require jquery-ui/widgets/autocomplete
//= require jquery-fileupload/basic
//= require foundation
//= require turbolinks
@@ -60,9 +61,12 @@
//= require legislation_annotatable
//= require watch_form_changes
//= require followable
//= require flaggable
//= require documentable
//= require imageable
//= require tree_navigator
//= require custom
//= require tag_autocomplete
var initialize_modules = function() {
App.Comments.initialize();
@@ -97,10 +101,12 @@ var initialize_modules = function() {
App.WatchFormChanges.initialize();
App.TreeNavigator.initialize();
App.Documentable.initialize();
App.Imageable.initialize();
App.TagAutocomplete.initialize();
};
$(function(){
Turbolinks.enableProgressBar()
Turbolinks.enableProgressBar();
$(document).ready(initialize_modules);
$(document).on('page:load', initialize_modules);

View File

@@ -1,101 +1,156 @@
App.Documentable =
initialize: ->
@initializeDirectUploads()
@initializeInterface()
initializeDirectUploads: ->
inputFiles = $('.js-document-attachment')
$.each inputFiles, (index, input) ->
App.Documentable.initializeDirectUploadInput(input)
$('input.document_ajax_attachment[type=file]').fileupload
$('#nested-documents').on 'cocoon:after-remove', (e, insertedItem) ->
App.Documentable.unlockUploads()
paramName: "document[attachment]"
$('#nested-documents').on 'cocoon:after-insert', (e, nested_document) ->
input = $(nested_document).find('.js-document-attachment')
App.Documentable.initializeDirectUploadInput(input)
if $(nested_document).closest('#nested-documents').find('.document:visible').length >= $('#nested-documents').data('max-documents-allowed')
App.Documentable.lockUploads()
initializeDirectUploadInput: (input) ->
inputData = @buildData([], input)
@initializeRemoveCachedDocumentLink(input, inputData)
$(input).fileupload
paramName: "attachment"
formData: null
add: (e, data) ->
wrapper = $(e.target).closest('.document')
index = $(e.target).data('index')
is_nested_document = $(e.target).data('nested-document')
$(wrapper).find('.progress-bar-placeholder').empty()
data.progressBar = $(wrapper).find('.progress-bar-placeholder').html('<div class="progress-bar"><div class="loading-bar uploading"></div></div>')
$(wrapper).find('.progress-bar-placeholder').css('display','block')
data.formData = {
"document[title]": $(wrapper).find('input.document-title').val() || data.files[0].name
"index": index,
"nested_document": is_nested_document
}
data = App.Documentable.buildFileUploadData(e, data)
App.Documentable.clearProgressBar(data)
App.Documentable.setProgressBar(data, 'uploading')
data.submit()
change: (e, data) ->
wrapper = $(e.target).parent()
$.each(data.files, (index, file)->
$(wrapper).find('.file-name').text(file.name)
)
$.each data.files, (index, file) ->
App.Documentable.setFilename(inputData, file.name)
fail: (e, data) ->
$(data.cachedAttachmentField).val("")
App.Documentable.clearFilename(data)
App.Documentable.setProgressBar(data, 'errors')
App.Documentable.clearInputErrors(data)
App.Documentable.setInputErrors(data)
$(data.destroyAttachmentLinkContainer).find("a.delete:not(.remove-nested)").remove()
$(data.addAttachmentLabel).addClass('error')
$(data.addAttachmentLabel).show()
done: (e, data) ->
$(data.cachedAttachmentField).val(data.result.cached_attachment)
App.Documentable.setTitleFromFile(data, data.result.filename)
App.Documentable.setProgressBar(data, 'complete')
App.Documentable.setFilename(data, data.result.filename)
App.Documentable.clearInputErrors(data)
$(data.addAttachmentLabel).hide()
$(data.wrapper).find(".attachment-actions").removeClass('small-12').addClass('small-6 float-right')
$(data.wrapper).find(".attachment-actions .action-remove").removeClass('small-3').addClass('small-12')
destroyAttachmentLink = $(data.result.destroy_link)
$(data.destroyAttachmentLinkContainer).html(destroyAttachmentLink)
$(destroyAttachmentLink).on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
App.Documentable.doDeleteCachedAttachmentRequest(this.href, data)
progress: (e, data) ->
progress = parseInt(data.loaded / data.total * 100, 10)
$(data.progressBar).find('.loading-bar').css 'width', progress + '%'
return
initializeInterface: ->
input_files = $('input.document_ajax_attachment[type=file]')
buildFileUploadData: (e, data) ->
data = @buildData(data, e.target)
return data
$.each input_files, (index, file) ->
wrapper = $(file).parent()
App.Documentable.watchRemoveDocumentbutton(wrapper)
buildData: (data, input) ->
wrapper = $(input).closest('.direct-upload')
data.input = input
data.wrapper = wrapper
data.progressBar = $(wrapper).find('.progress-bar-placeholder')
data.errorContainer = $(wrapper).find('.attachment-errors')
data.fileNameContainer = $(wrapper).find('p.file-name')
data.destroyAttachmentLinkContainer = $(wrapper).find('.action-remove')
data.addAttachmentLabel = $(wrapper).find('.action-add label')
data.cachedAttachmentField = $(wrapper).find("input[name$='[cached_attachment]']")
data.titleField = $(wrapper).find("input[name$='[title]']")
$(wrapper).find('.progress-bar-placeholder').css('display', 'block')
return data
watchRemoveDocumentbutton: (wrapper) ->
remove_document_button = $(wrapper).find('.remove-document')
$(remove_document_button).on 'click', (e) ->
clearFilename: (data) ->
$(data.fileNameContainer).text('')
$(data.fileNameContainer).hide()
clearInputErrors: (data) ->
$(data.errorContainer).find('small.error').remove()
clearProgressBar: (data) ->
$(data.progressBar).find('.loading-bar').removeClass('complete errors uploading').css('width', "0px")
setFilename: (data, file_name) ->
$(data.fileNameContainer).text(file_name)
$(data.fileNameContainer).show()
setProgressBar: (data, klass) ->
$(data.progressBar).find('.loading-bar').addClass(klass)
setTitleFromFile: (data, title) ->
if $(data.titleField).val() == ""
$(data.titleField).val(title)
setInputErrors: (data) ->
errors = '<small class="error">' + data.jqXHR.responseJSON.errors + '</small>'
$(data.errorContainer).append(errors)
lockUploads: ->
$('#max-documents-notice').removeClass('hide')
$('#new_document_link').addClass('hide')
unlockUploads: ->
$('#max-documents-notice').addClass('hide')
$('#new_document_link').removeClass('hide')
doDeleteCachedAttachmentRequest: (url, data) ->
$.ajax
type: "POST"
url: url
dataType: "json"
data: { "_method": "delete" }
complete: ->
$(data.cachedAttachmentField).val("")
$(data.addAttachmentLabel).show()
App.Documentable.clearFilename(data)
App.Documentable.clearInputErrors(data)
App.Documentable.clearProgressBar(data)
App.Documentable.unlockUploads()
$(data.wrapper).find(".attachment-actions").addClass('small-12').removeClass('small-6 float-right')
$(data.wrapper).find(".attachment-actions .action-remove").addClass('small-3').removeClass('small-12')
if $(data.input).data('nested-document') == true
$(data.wrapper).remove()
else
$(data.wrapper).find('a.remove-cached-attachment').remove()
initializeRemoveCachedDocumentLink: (input, data) ->
wrapper = $(input).closest(".direct-upload")
remove_document_link = $(wrapper).find('a.remove-cached-attachment')
$(remove_document_link).on 'click', (e) ->
e.preventDefault()
$(wrapper).remove()
$('#new_document_link').show()
$('.max-documents-notice').hide()
e.stopPropagation()
App.Documentable.doDeleteCachedAttachmentRequest(this.href, data)
uploadNestedDocument: (id, nested_document, result) ->
$('#' + id).replaceWith(nested_document)
@updateLoadingBar(id, result)
@initialize()
uploadPlainDocument: (id, nested_document, result) ->
$('#' + id).replaceWith(nested_document)
@updateLoadingBar(id, result)
@initialize()
updateLoadingBar: (id, result) ->
if result
$('#' + id).find('.loading-bar').addClass 'complete'
else
$('#' + id).find('.loading-bar').addClass 'errors'
$('#' + id).find('.progress-bar-placeholder').css('display','block')
new: (nested_fields) ->
$(".documents-list").append(nested_fields)
@initialize()
destroyNestedDocument: (id, notice) ->
removeDocument: (id) ->
$('#' + id).remove()
@updateNotice(notice)
replacePlainDocument: (id, notice, plain_document) ->
$('#' + id).replaceWith(plain_document)
@updateNotice(notice)
@initialize()
updateNotice: (notice) ->
if $('[data-alert]').length > 0
$('[data-alert]').replaceWith(notice)
else
$("body").append(notice)
updateNewDocumentButton: (link) ->
if $('.document').length >= $('.documents').data('max-documents')
$('#new_document_link').hide()
$('.max-documents-notice').removeClass('hide')
$('.max-documents-notice').show()
else if $('#new_document_link').length > 0
$('#new_document_link').replaceWith(link)
$('.max-documents-notice').hide()
else
$('.max-documents-notice').hide()
$(link).insertBefore('.documents hr:last')

View File

@@ -0,0 +1,4 @@
App.Flaggable =
update: (resource_id, button) ->
$("#" + resource_id + " .js-flag-actions").html(button).foundation()

View File

@@ -0,0 +1,166 @@
App.Imageable =
initialize: ->
inputFiles = $('.js-image-attachment')
$.each inputFiles, (index, input) ->
App.Imageable.initializeDirectUploadInput(input)
$('#nested-image').on 'cocoon:after-remove', (e, item) ->
$("#new_image_link").removeClass('hide')
$('#nested-image').on 'cocoon:before-insert', (e, nested_image) ->
if $(".js-image-attachment").length > 0
$(".js-image-attachment").closest('.image').remove()
$('#nested-image').on 'cocoon:after-insert', (e, nested_image) ->
$("#new_image_link").addClass('hide')
input = $(nested_image).find('.js-image-attachment')
App.Imageable.initializeDirectUploadInput(input)
initializeDirectUploadInput: (input) ->
inputData = @buildData([], input)
@initializeRemoveCachedImageLink(input, inputData)
$(input).fileupload
paramName: "attachment"
formData: null
add: (e, data) ->
data = App.Imageable.buildFileUploadData(e, data)
App.Imageable.clearProgressBar(data)
App.Imageable.setProgressBar(data, 'uploading')
data.submit()
change: (e, data) ->
$.each data.files, (index, file) ->
App.Imageable.setFilename(inputData, file.name)
fail: (e, data) ->
$(data.cachedAttachmentField).val("")
App.Imageable.clearFilename(data)
App.Imageable.setProgressBar(data, 'errors')
App.Imageable.clearInputErrors(data)
App.Imageable.setInputErrors(data)
App.Imageable.clearPreview(data)
$(data.destroyAttachmentLinkContainer).find("a.delete:not(.remove-nested)").remove()
$(data.addAttachmentLabel).addClass('error')
$(data.addAttachmentLabel).show()
done: (e, data) ->
$(data.cachedAttachmentField).val(data.result.cached_attachment)
App.Imageable.setTitleFromFile(data, data.result.filename)
App.Imageable.setProgressBar(data, 'complete')
App.Imageable.setFilename(data, data.result.filename)
App.Imageable.clearInputErrors(data)
$(data.addAttachmentLabel).hide()
$(data.wrapper).find(".attachment-actions").removeClass('small-12').addClass('small-6 float-right')
$(data.wrapper).find(".attachment-actions .action-remove").removeClass('small-3').addClass('small-12')
App.Imageable.setPreview(data)
destroyAttachmentLink = $(data.result.destroy_link)
$(data.destroyAttachmentLinkContainer).html(destroyAttachmentLink)
$(destroyAttachmentLink).on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
App.Imageable.doDeleteCachedAttachmentRequest(this.href, data)
progress: (e, data) ->
progress = parseInt(data.loaded / data.total * 100, 10)
$(data.progressBar).find('.loading-bar').css 'width', progress + '%'
return
buildFileUploadData: (e, data) ->
data = @buildData(data, e.target)
return data
buildData: (data, input) ->
wrapper = $(input).closest('.direct-upload')
data.input = input
data.wrapper = wrapper
data.progressBar = $(wrapper).find('.progress-bar-placeholder')
data.preview = $(wrapper).find('.image-preview')
data.errorContainer = $(wrapper).find('.attachment-errors')
data.fileNameContainer = $(wrapper).find('p.file-name')
data.destroyAttachmentLinkContainer = $(wrapper).find('.action-remove')
data.addAttachmentLabel = $(wrapper).find('.action-add label')
data.cachedAttachmentField = $(wrapper).find("input[name$='[cached_attachment]']")
data.titleField = $(wrapper).find("input[name$='[title]']")
$(wrapper).find('.progress-bar-placeholder').css('display', 'block')
return data
clearFilename: (data) ->
$(data.fileNameContainer).text('')
$(data.fileNameContainer).hide()
clearInputErrors: (data) ->
$(data.errorContainer).find('small.error').remove()
clearProgressBar: (data) ->
$(data.progressBar).find('.loading-bar').removeClass('complete errors uploading').css('width', "0px")
clearPreview: (data) ->
$(data.wrapper).find('.image-preview').remove()
setFilename: (data, file_name) ->
$(data.fileNameContainer).text(file_name)
$(data.fileNameContainer).show()
setProgressBar: (data, klass) ->
$(data.progressBar).find('.loading-bar').addClass(klass)
setTitleFromFile: (data, title) ->
if $(data.titleField).val() == ""
$(data.titleField).val(title)
setInputErrors: (data) ->
errors = '<small class="error">' + data.jqXHR.responseJSON.errors + '</small>'
$(data.errorContainer).append(errors)
setPreview: (data) ->
image_preview = '<div class="small-12 column text-center image-preview"><figure><img src="' + data.result.attachment_url + '" class="cached-image"/></figure></div>'
if $(data.preview).length > 0
$(data.preview).replaceWith(image_preview)
else
$(image_preview).insertBefore($(data.wrapper).find(".attachment-actions"))
data.preview = $(data.wrapper).find('.image-preview')
doDeleteCachedAttachmentRequest: (url, data) ->
$.ajax
type: "POST"
url: url
dataType: "json"
data: { "_method": "delete" }
complete: ->
$(data.cachedAttachmentField).val("")
$(data.addAttachmentLabel).show()
App.Imageable.clearFilename(data)
App.Imageable.clearInputErrors(data)
App.Imageable.clearProgressBar(data)
App.Imageable.clearPreview(data)
$('#new_image_link').removeClass('hide')
$(data.wrapper).find(".attachment-actions").addClass('small-12').removeClass('small-6 float-right')
$(data.wrapper).find(".attachment-actions .action-remove").addClass('small-3').removeClass('small-12')
if $(data.input).data('nested-image') == true
$(data.wrapper).remove()
else
$(data.wrapper).find('a.remove-cached-attachment').remove()
initializeRemoveCachedImageLink: (input, data) ->
wrapper = $(input).closest(".direct-upload")
remove_image_link = $(wrapper).find('a.remove-cached-attachment')
$(remove_image_link).on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
App.Imageable.doDeleteCachedAttachmentRequest(this.href, data)
removeImage: (id) ->
$('#' + id).remove()
$("#new_image_link").removeClass('hide')

View File

@@ -0,0 +1,34 @@
App.TagAutocomplete =
split: ( val ) ->
return (val.split( /,\s*/ ))
extractLast: ( term ) ->
return (App.TagAutocomplete.split( term ).pop())
init_autocomplete: ->
$('.tag-autocomplete').autocomplete
source: (request, response) ->
$.ajax
url: $('.tag-autocomplete').data('js-url'),
data: {search: App.TagAutocomplete.extractLast( request.term )},
type: 'GET',
dataType: 'json'
success: ( data ) ->
response( data );
minLength: 0,
search: ->
App.TagAutocomplete.extractLast( this.value );
focus: ->
return false;
select: ( event, ui ) -> (
terms = App.TagAutocomplete.split( this.value );
terms.pop();
terms.push( ui.item.value );
terms.push( "" );
this.value = terms.join( ", " );
return false;);
initialize: ->
App.TagAutocomplete.init_autocomplete();

View File

@@ -13,33 +13,99 @@
// 01. Global styles
// -----------------
$admin-color: #cf3638;
$admin-color: #245b80;
$sidebar: #245b80;
$sidebar-hover: #25597c;
$sidebar-active: #f4fcd0;
.admin {
h2 {
font-weight: 100;
margin-bottom: $line-height;
&.title {
text-transform: uppercase;
}
}
.back {
float: none;
}
.header {
border: 0;
}
.top-links {
background: darken($admin-color, 15%);
}
.back-web {
padding-top: $line-height / 4;
text-decoration: underline;
background: #000;
}
.top-bar {
background: $admin-color !important;
background: #fff !important;
border-bottom: 1px solid #eee;
box-shadow: 0 2px 2px #eee;
color: #000;
height: auto;
[class^="icon-"]:not(.icon-circle) {
font-size: $base-font-size;
}
}
.top-bar-title {
h1 {
margin-bottom: 0;
margin-top: $line-height / 2;
@include breakpoint(medium) {
margin-left: $line-height / 2;
}
small {
color: #000;
text-transform: uppercase;
}
}
a {
color: #000 !important;
line-height: $line-height !important;
@include breakpoint(medium) {
line-height: $line-height !important;
}
}
}
.top-bar .menu > li {
@include breakpoint(medium) {
height: auto !important;
padding-top: $line-height / 2;
}
a {
color: #000 !important;
}
}
.menu-icon.dark {
&::after,
&:hover::after {
background: #000 !important;
box-shadow: 0 7px 0 #000, 0 14px 0 #000 !important;
}
}
.notifications .icon-circle {
color: $admin-color;
}
.dropdown.menu > .is-dropdown-submenu-parent > a::after {
border-color: #000 transparent transparent;
}
.fieldset {
@@ -54,7 +120,8 @@ $admin-color: #cf3638;
}
}
th, td {
th,
td {
text-align: left;
&.text-center {
@@ -110,6 +177,7 @@ $admin-color: #cf3638;
.menu.simple .active {
border-bottom: 2px solid $admin-color;
color: $admin-color;
font-weight: bold;
}
.tabs-panel {
@@ -195,6 +263,8 @@ $admin-color: #cf3638;
// -----------
.admin-sidebar {
background: $sidebar;
background: linear-gradient(to bottom, #245b80 0%, #488fb5 100%);
border-right: 1px solid $border;
@include breakpoint(medium) {
@@ -208,7 +278,7 @@ $admin-color: #cf3638;
padding: 0;
[class^="icon-"] {
color: $admin-color;
color: #fff;
display: inline-block;
font-size: rem-calc(24);
line-height: $line-height;
@@ -219,39 +289,32 @@ $admin-color: #cf3638;
}
li {
background: #fff;
margin: 0;
outline: 0;
ul {
margin-left: $line-height / 1.5;
border-left: 1px solid $border;
border-left: 1px solid $sidebar-hover;
padding-left: $line-height / 2;
}
&.section-title {
border-bottom: 1px solid $border;
}
&.active a {
background: #f3f6f7;
border-radius: rem-calc(6);
color: $admin-color;
background: $sidebar-hover;
border-left: 2px solid $sidebar-active;
font-weight: bold;
}
}
li a {
color: $text;
color: #fff;
display: block;
line-height: rem-calc(48);
padding-left: rem-calc(12);
vertical-align: top;
&:hover {
background: #f3f6f7;
border-radius: rem-calc(6);
color: $admin-color;
background: $sidebar-hover;
color: #fff;
text-decoration: none;
}
}
@@ -259,7 +322,13 @@ $admin-color: #cf3638;
.is-accordion-submenu-parent {
> a::after {
border-color: $admin-color transparent transparent;
border: 0;
content: '\61' !important;
font-family: "icons" !important;
height: auto;
position: absolute !important;
right: 30px;
top: 6px !important;
}
}

View File

@@ -16,4 +16,5 @@
@import 'annotator_overrides';
@import 'jquery-ui/datepicker';
@import 'datepicker_overrides';
@import 'documentable';
@import 'jquery-ui/autocomplete';
@import 'autocomplete_overrides';

View File

@@ -0,0 +1,40 @@
// Overrides styles of jquery-ui/autocomplete
//
/* Autocomplete
----------------------------------*/
.ui-autocomplete {
position: absolute;
cursor: default;
}
/* workarounds */
* html .ui-autocomplete {
width: 1px;
} /* without this, the menu expands to 100% in IE6 */
/* Menu
----------------------------------*/
.ui-menu {
list-style: none;
padding: $line-height / 4 $line-height / 3;
display: block;
background: #fff;
border: 1px solid $border;
font-size: $small-font-size;
.ui-menu-item {
.ui-menu-item-wrapper {
padding: $line-height / 4 $line-height / 3;
position: relative;
}
.ui-state-hover,
.ui-state-active {
background: #ececec;
border-radius: rem-calc(6);
}
}
}

View File

@@ -1,5 +1,6 @@
.communities-show {
.button.disabled, .button[disabled] {
.button.disabled,
.button[disabled] {
pointer-events: none;
}

View File

@@ -46,16 +46,16 @@
width: rem-calc(30);
&:hover {
text-decoration: none;
text-decoration: none;
}
}
.ui-datepicker-prev::after {
content: '\62';
content: '\62';
}
.ui-datepicker-next::after {
content: '\63';
content: '\63';
}
table {

View File

@@ -1,59 +0,0 @@
.progress-bar-placeholder {
display: none;
}
.document-form {
.document .file-name {
margin-top: 0;
}
.progress-bar-placeholder {
margin-bottom: 15px;
}
.document .loading-bar.errors {
margin-top: $line-height * 2;
}
}
.document {
.button {
font-weight: normal;
}
.progress-bar {
width: 100%;
background-color: $light-gray;
}
input.document_ajax_attachment[type=file]{
display: none;
}
.file-name {
margin-top: $line-height / 2;
}
.loading-bar {
height: 5px;
width: 0;
transition: width 500ms ease-out;
&.uploading {
background-color: $dark-gray;
}
&.complete {
background-color: $success-color;
width: 100%;
}
&.errors {
background-color: $alert-color;
width: 100%;
margin-top: $line-height / 2;
}
}
.loading-bar.no-transition {
transition: none;
}
}

View File

@@ -197,6 +197,10 @@
content: '\53';
}
.icon-image::before {
content: '\68';
}
.icon-notification::before {
content: '\6e';
}

View File

@@ -206,19 +206,40 @@ a {
.menu.simple {
border-bottom: 1px solid $border;
margin-bottom: $line-height;
clear: both;
margin-bottom: $line-height / 2;
li {
padding-bottom: rem-calc(7);
margin-right: $line-height / 2;
@include breakpoint(medium) {
margin-right: $line-height * 1.5;
}
a {
color: $text-medium;
color: $text;
display: inline-block;
font-weight: bold;
position: relative;
text-align: left;
&:hover {
color: $link;
}
}
&.active {
border-bottom: 2px solid $brand;
color: $brand;
}
&:not(.active) {
margin-bottom: $line-height / 3;
}
}
h2 {
font-size: $base-font-size;
}
}
@@ -313,10 +334,6 @@ a {
}
}
.no-max-width {
max-width: none;
}
.button.float-right ~ .button.float-right {
margin: 0 $line-height / 2;
}
@@ -380,7 +397,11 @@ header {
.top-bar-title a {
@include logo;
line-height: rem-calc(80) !important;
line-height: rem-calc(80);
@include breakpoint(medium) {
line-height: rem-calc(80);
}
&:hover {
text-decoration: none;
@@ -2284,68 +2305,7 @@ table {
}
}
// 20. Documents
.document-form form {
.radio-buttons {
label {
margin-right: $line-height;
}
}
.source-option-link {
input {
padding-bottom: 0;
}
.error {
margin-bottom: $line-height;
}
label {
&.error {
margin-bottom: 0;
}
}
}
.source-option-file {
.file-name {
label {
@include breakpoint(small medium) {
float: none;
}
@include breakpoint(large) {
float: left;
}
}
p {
@include breakpoint(small medium) {
float: none;
margin-top: 0;
margin-left: 0;
margin-bottom: 0;
}
@include breakpoint(large) {
float: left;
margin-bottom: 0;
margin-top: $line-height / 2;
margin-left: $line-height;
}
}
}
}
.attachment-errors {
margin-bottom: $line-height;
}
}
// 19. Documents
.documents-list {
table {

View File

@@ -459,11 +459,11 @@ $border-dark: darken($border, 10%);
span {
vertical-align: inherit;
font-style: normal;
}
a {
text-decoration: underline;
color: $text-medium;
}
.see-changes {
color: $text-medium;
text-decoration: underline;
}
}
@@ -477,6 +477,7 @@ $border-dark: darken($border, 10%);
}
.draft-allegation {
@include breakpoint(medium) {
display: flex;
padding-left: 0.9375rem;
@@ -493,7 +494,6 @@ $border-dark: darken($border, 10%);
}
}
// Panel calcs for desktop
@media screen and (min-width: 40em) {
.calc-index {
width: calc(35% - 25px);
@@ -509,6 +509,7 @@ $border-dark: darken($border, 10%);
width: rem-calc(50);
.draft-panel {
.panel-title {
display: none;
}
@@ -912,19 +913,15 @@ $border-dark: darken($border, 10%);
display: inline-block;
}
}
.show-for-medium {
.panel-title {
display: none;
}
}
}
}
}
// 08. Legislation changes
// -----------------
.legislation-changes {
ul {
list-style: none;
margin-left: 0;
@@ -936,35 +933,36 @@ $border-dark: darken($border, 10%);
margin-right: 0.25rem;
content: '';
}
}
}
.changes-link {
display: block;
margin-left: 1rem;
font-size: $small-font-size;
.changes-link {
display: block;
margin-left: 1rem;
font-size: $small-font-size;
@include breakpoint(medium) {
display: inline-block;
}
@include breakpoint(medium) {
display: inline-block;
}
a {
span {
text-decoration: underline;
}
a {
.icon-external {
text-decoration: none;
color: #999;
line-height: 0;
vertical-align: sub;
margin-left: 0.5rem;
}
span {
text-decoration: underline;
}
&:active,
&:focus,
&:hover {
text-decoration: none;
}
}
.icon-external {
text-decoration: none;
color: #999;
line-height: 0;
vertical-align: sub;
margin-left: 0.5rem;
}
&:active,
&:focus,
&:hover {
text-decoration: none;
}
}
}
@@ -972,6 +970,7 @@ $border-dark: darken($border, 10%);
// 09. Legislation comments
// -----------------
.legislation-comments {
.pull-right {
@@ -1020,6 +1019,7 @@ $border-dark: darken($border, 10%);
// 10. Legislation draft comment
// -----------------
.legislation-comment {
.annotation-share-comment {

View File

@@ -32,8 +32,8 @@
}
}
//02. Orbit bullet
// 02. Orbit bullet
// ----------------
@mixin orbit-bullets {
@include disable-mouse-outline;
position: relative;
@@ -58,3 +58,72 @@
}
}
}
// 02. Direct uploads
// ------------------
@mixin direct-uploads {
.cached-image {
max-width: 150px;
max-height: 150px;
}
.progress-bar-placeholder {
display: none;
margin-bottom: $line-height;
}
.document,
.image {
.document-attachment,
.image-attachment {
padding-left:0;
p{
margin-bottom: 0;
}
}
input.js-document-attachment,
input.js-image-attachment{
display: none;
}
}
.button {
font-weight: normal;
}
.progress-bar {
width: 100%;
background-color: $light-gray;
}
.file-name {
margin-top: 0;
}
.loading-bar {
height: 5px;
width: 0;
transition: width 500ms ease-out;
&.uploading {
background-color: $dark-gray;
}
&.complete {
background-color: $success-color;
}
&.errors {
background-color: $alert-color;
margin-top: $line-height / 2;
}
}
.loading-bar.no-transition {
transition: none;
}
}

View File

@@ -249,14 +249,14 @@
.proposal-form,
.budget-investment-form,
.spending-proposal-form,
.document-form,
.topic-new,
.topic-form {
.topic-form {
.icon-debates,
.icon-proposals,
.icon-budget,
.icon-documents {
.icon-documents,
.icon-image {
font-size: rem-calc(50);
line-height: $line-height;
opacity: 0.5;
@@ -267,7 +267,8 @@
}
.icon-proposals,
.icon-documents {
.icon-documents,
.icon-image {
color: $proposals;
}
@@ -301,14 +302,22 @@
.proposal-form,
.topic-form,
.topic-new,
.document-form {
.topic-new {
.recommendations li::before {
color: $proposals;
}
}
.budget-investment-new,
.proposal-form,
.proposal-edit,
.new_poll_question,
.edit_poll_question {
@include direct-uploads;
}
// 03. Show participation
// ----------------------
@@ -640,6 +649,77 @@
}
}
.budget-investments-list .budget-investment,
.proposals-list .proposal {
.no-image {
background: $brand;
}
}
.budget-investments-list .budget-investment,
.proposals-list .proposal {
@include breakpoint(small) {
.no-image {
width: 100%;
max-width: 300px;
margin: 0 auto;
}
.no-image::before {
content: '';
display: block;
padding-top: 100%;
}
h3 {
font-size: 1.3rem;
}
.column:first-child {
text-align: center;
}
}
@include breakpoint(medium) {
.panel {
padding: 0 0.75rem 0 0;
.no-image {
height: 245px;
width: 140px;
}
}
h3 {
font-size: 1.4rem;
}
.column:first-child {
overflow: hidden;
}
.column:nth-child(2) {
float: left;
}
.column:last-child:not(:first-child) {
padding-top: 0.75rem;
}
img {
max-width: 12rem;
}
.budget-investment-content {
ul {
margin-bottom: 0;
}
}
}
}
.debate,
.proposal,
.investment-project,
@@ -769,12 +849,6 @@
display: none;
}
.document-form{
max-width: 75rem;
margin-left: auto;
margin-right: auto;
}
.more-info {
clear: both;
color: $text-medium;
@@ -859,6 +933,19 @@
}
}
.budget-investment-show {
figure {
margin: rem-calc(10) 0 0;
display: inline-block;
figcaption {
font-size: $small-font-size;
margin-top: rem-calc(10);
}
}
}
.investment-project-show .supports,
.budget-investment-show .supports {
border: 0;

View File

@@ -0,0 +1,10 @@
class Admin::Poll::BaseController < Admin::BaseController
helper_method :namespace
private
def namespace
"admin"
end
end

View File

@@ -1,4 +1,4 @@
class Admin::Poll::BoothAssignmentsController < Admin::BaseController
class Admin::Poll::BoothAssignmentsController < Admin::Poll::BaseController
before_action :load_poll, except: [:create, :destroy]
@@ -15,7 +15,7 @@ class Admin::Poll::BoothAssignmentsController < Admin::BaseController
end
def show
@booth_assignment = @poll.booth_assignments.includes(:final_recounts, :voters,
@booth_assignment = @poll.booth_assignments.includes(:total_results, :voters,
officer_assignments: [officer: [:user]]).find(params[:id])
@voters_by_date = @booth_assignment.voters.group_by {|v| v.created_at.to_date}
end

View File

@@ -1,4 +1,4 @@
class Admin::Poll::BoothsController < Admin::BaseController
class Admin::Poll::BoothsController < Admin::Poll::BaseController
load_and_authorize_resource class: 'Poll::Booth'
def index

View File

@@ -1,4 +1,4 @@
class Admin::Poll::OfficerAssignmentsController < Admin::BaseController
class Admin::Poll::OfficerAssignmentsController < Admin::Poll::BaseController
before_action :load_poll
before_action :redirect_if_blank_required_params, only: [:by_officer]
@@ -18,7 +18,7 @@ class Admin::Poll::OfficerAssignmentsController < Admin::BaseController
@officer = ::Poll::Officer.includes(:user).find(officer_assignment_params[:officer_id])
@officer_assignments = ::Poll::OfficerAssignment.
joins(:booth_assignment).
includes(:final_recounts, booth_assignment: :booth).
includes(:total_results, booth_assignment: :booth).
where("officer_id = ? AND poll_booth_assignments.poll_id = ?", @officer.id, @poll.id).
order(:date)
end

View File

@@ -1,4 +1,4 @@
class Admin::Poll::OfficersController < Admin::BaseController
class Admin::Poll::OfficersController < Admin::Poll::BaseController
load_and_authorize_resource :officer, class: "Poll::Officer", except: [:edit, :show]
def index

View File

@@ -1,4 +1,4 @@
class Admin::Poll::PollsController < Admin::BaseController
class Admin::Poll::PollsController < Admin::Poll::BaseController
load_and_authorize_resource
before_action :load_search, only: [:search_booths, :search_questions, :search_officers]

View File

@@ -1,4 +1,4 @@
class Admin::Poll::QuestionsController < Admin::BaseController
class Admin::Poll::QuestionsController < Admin::Poll::BaseController
include CommentableActions
load_and_authorize_resource :poll
@@ -22,7 +22,6 @@ class Admin::Poll::QuestionsController < Admin::BaseController
def create
@question.author = @question.proposal.try(:author) || current_user
recover_documents_from_cache(@question)
if @question.save
redirect_to admin_question_path(@question)
@@ -32,7 +31,6 @@ class Admin::Poll::QuestionsController < Admin::BaseController
end
def show
@document = Document.new(documentable: @question)
end
def edit
@@ -59,7 +57,7 @@ class Admin::Poll::QuestionsController < Admin::BaseController
def question_params
params.require(:poll_question).permit(:poll_id, :title, :question, :description, :proposal_id, :valid_answers, :video_url,
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id])
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy])
end
def search_params

View File

@@ -1,9 +1,9 @@
class Admin::Poll::RecountsController < Admin::BaseController
class Admin::Poll::RecountsController < Admin::Poll::BaseController
before_action :load_poll
def index
@booth_assignments = @poll.booth_assignments.
includes(:booth, :final_recounts, :voters).
includes(:booth, :total_results, :voters).
order("poll_booths.name").
page(params[:page]).per(50)
end

View File

@@ -1,4 +1,4 @@
class Admin::Poll::ResultsController < Admin::BaseController
class Admin::Poll::ResultsController < Admin::Poll::BaseController
before_action :load_poll
def index

View File

@@ -1,4 +1,4 @@
class Admin::Poll::ShiftsController < Admin::BaseController
class Admin::Poll::ShiftsController < Admin::Poll::BaseController
before_action :load_booth
before_action :load_polls

View File

@@ -1,7 +1,7 @@
class Admin::SettingsController < Admin::BaseController
def index
all_settings = (Setting.all).group_by { |s| s.type }
all_settings = Setting.all.group_by { |s| s.type }
@settings = all_settings['common']
@feature_flags = all_settings['feature']
@banner_styles = all_settings['banner-style']

View File

@@ -44,12 +44,10 @@ module Budgets
set_comment_flags(@comment_tree.comments)
load_investment_votes(@investment)
@investment_ids = [@investment.id]
@document = Document.new(documentable: @investment)
end
def create
@investment.author = current_user
recover_documents_from_cache(@investment)
if @investment.save
Mailer.budget_investment_created(@investment).deliver_later
@@ -105,9 +103,11 @@ module Budgets
end
def investment_params
params.require(:budget_investment).permit(:title, :description, :external_url, :heading_id, :tag_list,
:organization_name, :location, :terms_of_service,
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id])
params.require(:budget_investment)
.permit(:title, :description, :external_url, :heading_id, :tag_list,
:organization_name, :location, :terms_of_service,
image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy],
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy])
end
def load_ballot

View File

@@ -62,10 +62,7 @@ module CommentableActions
end
def update
resource.assign_attributes(strong_params)
recover_documents_from_cache(resource)
if resource.save
if resource.update(strong_params)
redirect_to resource, notice: t("flash.actions.update.#{resource_name.underscore}")
else
load_categories
@@ -117,11 +114,4 @@ module CommentableActions
nil
end
def recover_documents_from_cache(resource)
return false unless resource.try(:documents)
resource.documents = resource.documents.each do |document|
document.set_attachment_from_cached_attachment if document.cached_attachment.present?
end
end
end

View File

@@ -0,0 +1,49 @@
class DirectUploadsController < ApplicationController
include DirectUploadsHelper
include ActionView::Helpers::UrlHelper
before_action :authenticate_user!
load_and_authorize_resource except: :create
skip_authorization_check only: :create
helper_method :render_destroy_upload_link
def create
@direct_upload = DirectUpload.new(direct_upload_params.merge(user: current_user, attachment: params[:attachment]))
if @direct_upload.valid?
@direct_upload.save_attachment
@direct_upload.relation.set_cached_attachment_from_attachment
render json: { cached_attachment: @direct_upload.relation.cached_attachment,
filename: @direct_upload.relation.attachment.original_filename,
destroy_link: render_destroy_upload_link(@direct_upload).html_safe,
attachment_url: @direct_upload.relation.attachment.url
}
else
@direct_upload.destroy_attachment
render json: { errors: @direct_upload.errors[:attachment].join(", ") },
status: 422
end
end
def destroy
@direct_upload = DirectUpload.new(direct_upload_params.merge(user: current_user) )
@direct_upload.relation.set_attachment_from_cached_attachment
if @direct_upload.destroy_attachment
render json: :ok
else
render json: :error
end
end
private
def direct_upload_params
params.require(:direct_upload)
.permit(:resource, :resource_type, :resource_id, :resource_relation,
:attachment, :cached_attachment, attachment_attributes: [])
end
end

View File

@@ -1,29 +1,7 @@
class DocumentsController < ApplicationController
before_action :authenticate_user!
before_action :find_documentable, except: :destroy
before_action :prepare_new_document, only: [:new, :new_nested]
before_action :prepare_document_for_creation, only: :create
load_and_authorize_resource except: :upload
skip_authorization_check only: :upload
def new
end
def new_nested
end
def create
recover_attachments_from_cache
if @document.save
flash[:notice] = t "documents.actions.create.notice"
redirect_to params[:from]
else
flash[:alert] = t "documents.actions.create.alert"
render :new
end
end
load_and_authorize_resource
def destroy
respond_to do |format|
@@ -45,57 +23,4 @@ class DocumentsController < ApplicationController
end
end
def destroy_upload
@document = Document.new(cached_attachment: params[:path])
@document.set_attachment_from_cached_attachment
@document.cached_attachment = nil
@document.documentable = @documentable
if @document.attachment.destroy
flash.now[:notice] = t "documents.actions.destroy.notice"
else
flash.now[:alert] = t "documents.actions.destroy.alert"
end
render :destroy
end
def upload
@document = Document.new(document_params.merge(user: current_user))
@document.documentable = @documentable
if @document.valid?
@document.attachment.save
@document.set_cached_attachment_from_attachment(URI(request.url))
else
@document.attachment.destroy
end
end
private
def document_params
params.require(:document).permit(:title, :documentable_type, :documentable_id,
:attachment, :cached_attachment, :user_id)
end
def find_documentable
@documentable = params[:documentable_type].constantize.find_or_initialize_by(id: params[:documentable_id])
end
def prepare_new_document
@document = Document.new(documentable: @documentable, user_id: current_user.id)
end
def prepare_document_for_creation
@document = Document.new(document_params)
@document.documentable = @documentable
@document.user = current_user
end
def recover_attachments_from_cache
if @document.attachment.blank? && @document.cached_attachment.present?
@document.set_attachment_from_cached_attachment
end
end
end

View File

@@ -0,0 +1,26 @@
class ImagesController < ApplicationController
before_action :authenticate_user!
load_and_authorize_resource
def destroy
respond_to do |format|
format.html do
if @image.destroy
flash[:notice] = t "images.actions.destroy.notice"
else
flash[:alert] = t "images.actions.destroy.alert"
end
redirect_to params[:from]
end
format.js do
if @image.destroy
flash.now[:notice] = t "images.actions.destroy.notice"
else
flash.now[:alert] = t "images.actions.destroy.alert"
end
end
end
end
end

View File

@@ -30,7 +30,7 @@ class Legislation::AnnotationsController < ApplicationController
def create
if !@process.allegations_phase.open? || @draft_version.final_version?
render(json: {}, status: :not_found) && (return)
render(json: {}, status: :not_found) && return
end
existing_annotation = @draft_version.annotations.where(

View File

@@ -30,7 +30,7 @@ class Legislation::AnswersController < Legislation::BaseController
def answer_params
params.require(:legislation_answer).permit(
:legislation_question_option_id,
:legislation_question_option_id
)
end

View File

@@ -4,7 +4,7 @@ class Management::DocumentVerificationsController < Management::BaseController
before_action :set_document, only: :check
def index
@document_verification = Verification::Management::Document.new()
@document_verification = Verification::Management::Document.new
end
def check

View File

@@ -7,7 +7,7 @@ class Officing::PollsController < Officing::BaseController
def final
@polls = if current_user.poll_officer?
current_user.poll_officer.final_days_assigned_polls.select {|poll| poll.ends_at > 2.week.ago && poll.expired?}
current_user.poll_officer.final_days_assigned_polls.select {|poll| poll.ends_at > 2.weeks.ago && poll.expired?}
else
[]
end

View File

@@ -52,20 +52,19 @@ class Officing::ResultsController < Officing::BaseController
go_back_to_new if question.blank?
results.each_pair do |answer_index, count|
if count.present?
answer = question.valid_answers[answer_index.to_i]
go_back_to_new if question.blank?
next unless count.present?
answer = question.valid_answers[answer_index.to_i]
go_back_to_new if question.blank?
partial_result = ::Poll::PartialResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date],
question_id: question_id,
answer: answer)
partial_result.officer_assignment_id = @officer_assignment.id
partial_result.amount = count.to_i
partial_result.author = current_user
partial_result.origin = 'booth'
@results << partial_result
end
partial_result = ::Poll::PartialResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date],
question_id: question_id,
answer: answer)
partial_result.officer_assignment_id = @officer_assignment.id
partial_result.amount = count.to_i
partial_result.author = current_user
partial_result.origin = 'booth'
@results << partial_result
end
end

View File

@@ -10,8 +10,6 @@ class Polls::QuestionsController < ApplicationController
@comment_tree = CommentTree.new(@commentable, params[:page], @current_order)
set_comment_flags(@comment_tree.comments)
@document = Document.new(documentable: @question)
question_answer = @question.answers.where(author_id: current_user.try(:id)).first
@answers_by_question_id = {@question.id => question_answer.try(:answer)}
end

View File

@@ -19,13 +19,11 @@ class ProposalsController < ApplicationController
def show
super
@notifications = @proposal.notifications
@document = Document.new(documentable: @proposal)
redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal)
end
def create
@proposal = Proposal.new(proposal_params.merge(author: current_user))
recover_documents_from_cache(@proposal)
if @proposal.save
redirect_to share_proposal_path(@proposal), notice: I18n.t('flash.actions.create.proposal')
@@ -78,7 +76,8 @@ class ProposalsController < ApplicationController
def proposal_params
params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url,
:responsible_name, :tag_list, :terms_of_service, :geozone_id,
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id])
image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy],
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] )
end
def retired_params

View File

@@ -0,0 +1,11 @@
class TagsController < ApplicationController
load_and_authorize_resource class: ActsAsTaggableOn::Tag
respond_to :json
def suggest
@tags = ActsAsTaggableOn::Tag.search(params[:search]).map(&:name)
respond_with @tags
end
end

View File

@@ -25,7 +25,7 @@ module AdminHelper
end
def menu_polls?
["polls", "questions", "officers", "booths", "officer_assignments", "booth_assignments", "recounts", "results", "shifts"].include? controller_name
%w[polls questions officers booths officer_assignments booth_assignments recounts results shifts].include? controller_name
end
def menu_profiles?

View File

@@ -0,0 +1,16 @@
module DirectUploadsHelper
def render_destroy_upload_link(direct_upload)
label = direct_upload.resource_relation == "image" ? "images" : "documents"
link_to t("#{label}.form.delete_button"),
direct_upload_destroy_url("direct_upload[resource_type]": direct_upload.resource_type,
"direct_upload[resource_id]": direct_upload.resource_id,
"direct_upload[resource_relation]": direct_upload.resource_relation,
"direct_upload[cached_attachment]": direct_upload.relation.cached_attachment,
format: :json),
method: :delete,
remote: true,
class: "delete remove-cached-attachment"
end
end

View File

@@ -8,12 +8,12 @@ module DocumentablesHelper
documentable.class.max_documents_allowed
end
def max_file_size(documentable)
bytes_to_mega(documentable.class.max_file_size)
def max_file_size(documentable_class)
bytes_to_mega(documentable_class.max_file_size)
end
def accepted_content_types(documentable)
documentable.class.accepted_content_types
def accepted_content_types(documentable_class)
documentable_class.accepted_content_types
end
def accepted_content_types_extensions(documentable_class)
@@ -22,16 +22,16 @@ module DocumentablesHelper
.join(",")
end
def humanized_accepted_content_types(documentable)
documentable.class.accepted_content_types
def documentable_humanized_accepted_content_types(documentable_class)
documentable_class.accepted_content_types
.collect{ |content_type| content_type.split("/").last }
.join(", ")
end
def documentables_note(documentable)
t "documents.form.note", max_documents_allowed: max_documents_allowed(documentable),
accepted_content_types: humanized_accepted_content_types(documentable),
max_file_size: max_file_size(documentable)
accepted_content_types: documentable_humanized_accepted_content_types(documentable.class),
max_file_size: max_file_size(documentable.class)
end
def max_documents_allowed?(documentable)

View File

@@ -4,7 +4,7 @@ module DocumentsHelper
document.attachment_file_name
end
def errors_on_attachment(document)
def document_errors_on_attachment(document)
document.errors[:attachment].join(', ') if document.errors.key?(:attachment)
end
@@ -12,78 +12,46 @@ module DocumentsHelper
bytes / Numeric::MEGABYTE
end
def document_nested_field_name(document, index, field)
parent = document.documentable_type.parameterize.underscore
"#{parent.parameterize}[documents_attributes][#{index}][#{field}]"
end
def document_nested_field_id(document, index, field)
parent = document.documentable_type.parameterize.underscore
"#{parent.parameterize}_documents_attributes_#{index}_#{field}"
end
def document_nested_field_wrapper_id(index)
"document_#{index}"
end
def render_destroy_document_link(document, index)
if document.persisted?
def render_destroy_document_link(builder, document)
if !document.persisted? && document.cached_attachment.present?
link_to t('documents.form.delete_button'),
document_path(document, index: index, nested_document: true),
method: :delete,
remote: true,
data: { confirm: t('documents.actions.destroy.confirm') },
class: "delete float-right"
elsif !document.persisted? && document.cached_attachment.present?
link_to t('documents.form.delete_button'),
destroy_upload_documents_path(path: document.cached_attachment,
nested_document: true,
index: index,
documentable_type: document.documentable_type,
documentable_id: document.documentable_id),
method: :delete,
remote: true,
class: "delete float-right"
direct_upload_destroy_url("direct_upload[resource_type]": document.documentable_type,
"direct_upload[resource_id]": document.documentable_id,
"direct_upload[resource_relation]": "documents",
"direct_upload[cached_attachment]": document.cached_attachment),
method: :delete,
remote: true,
class: "delete remove-cached-attachment"
else
link_to t('documents.form.delete_button'),
"#",
class: "delete float-right remove-document"
link_to_remove_association t('documents.form.delete_button'), builder, class: "delete remove-document"
end
end
def render_attachment(document, index)
html = file_field_tag :attachment,
accept: accepted_content_types_extensions(document.documentable_type.constantize),
class: 'document_ajax_attachment',
data: {
url: document_direct_upload_url(document),
cached_attachment_input_field: document_nested_field_id(document, index, :cached_attachment),
multiple: false,
index: index,
nested_document: true
},
name: document_nested_field_name(document, index, :attachment),
id: document_nested_field_id(document, index, :attachment)
if document.attachment.blank? && document.cached_attachment.blank?
klass = document.errors[:attachment].any? ? "error" : ""
html += label_tag document_nested_field_id(document, index, :attachment),
t("documents.form.attachment_label"),
class: "button hollow #{klass}"
if document.errors[:attachment].any?
html += content_tag :small, class: "error" do
errors_on_attachment(document)
end
end
end
def render_attachment(builder, document)
klass = document.errors[:attachment].any? ? "error" : ""
klass = document.persisted? || document.cached_attachment.present? ? " hide" : ""
html = builder.label :attachment,
t("documents.form.attachment_label"),
class: "button hollow #{klass}"
html += builder.file_field :attachment,
label: false,
accept: accepted_content_types_extensions(document.documentable_type.constantize),
class: 'js-document-attachment',
data: {
url: document_direct_upload_url(document),
nested_document: true
}
html
end
def document_direct_upload_url(document)
upload_documents_url(
documentable_type: document.documentable_type,
documentable_id: document.documentable_id,
format: :js
)
direct_uploads_url("direct_upload[resource_type]": document.documentable_type,
"direct_upload[resource_id]": document.documentable_id,
"direct_upload[resource_relation]": "documents")
end
end

View File

@@ -0,0 +1,40 @@
module ImageablesHelper
def can_destroy_image?(imageable)
imageable.image.present? && can?(:destroy, imageable.image)
end
def imageable_class(imageable)
imageable.class.name.parameterize('_')
end
def imageable_max_file_size
bytesToMeg(Image::MAX_IMAGE_SIZE)
end
def bytesToMeg(bytes)
bytes / Numeric::MEGABYTE
end
def imageable_accepted_content_types
Image::ACCEPTED_CONTENT_TYPE
end
def imageable_accepted_content_types_extensions
Image::ACCEPTED_CONTENT_TYPE
.collect{ |content_type| ".#{content_type.split("/").last}" }
.join(",")
end
def imageable_humanized_accepted_content_types
Image::ACCEPTED_CONTENT_TYPE
.collect{ |content_type| content_type.split("/").last }
.join(", ")
end
def imageables_note(imageable)
t "images.form.note", accepted_content_types: imageable_humanized_accepted_content_types,
max_file_size: imageable_max_file_size
end
end

View File

@@ -0,0 +1,79 @@
module ImagesHelper
def image_absolute_url(image, version)
return "" unless image
if Paperclip::Attachment.default_options[:storage] == :filesystem
URI(request.url) + image.attachment.url(version)
else
investment.image_url(version)
end
end
def image_first_recommendation(image)
t "images.#{image.imageable.class.name.parameterize.underscore}.recommendation_one_html",
title: image.imageable.title
end
def image_attachment_file_name(image)
image.attachment_file_name
end
def image_errors_on_attachment(image)
image.errors[:attachment].join(', ') if image.errors.key?(:attachment)
end
def image_bytesToMeg(bytes)
bytes / Numeric::MEGABYTE
end
def image_class(image)
image.persisted? ? "persisted-image" : "cached-image"
end
def render_destroy_image_link(builder, image)
if !image.persisted? && image.cached_attachment.present?
link_to t('images.form.delete_button'),
direct_upload_destroy_url("direct_upload[resource_type]": image.imageable_type,
"direct_upload[resource_id]": image.imageable_id,
"direct_upload[resource_relation]": "image",
"direct_upload[cached_attachment]": image.cached_attachment),
method: :delete,
remote: true,
class: "delete remove-cached-attachment"
else
link_to_remove_association t('images.form.delete_button'), builder, class: "delete remove-image"
end
end
def render_image_attachment(builder, imageable, image)
klass = image.errors[:attachment].any? ? "error" : ""
klass = image.persisted? || image.cached_attachment.present? ? " hide" : ""
html = builder.label :attachment,
t("images.form.attachment_label"),
class: "button hollow #{klass}"
html += builder.file_field :attachment,
label: false,
accept: imageable_accepted_content_types_extensions,
class: 'js-image-attachment',
data: {
url: image_direct_upload_url(imageable),
nested_image: true
}
html
end
def render_image(image, version, show_caption = true)
version = image.persisted? ? version : :original
render partial: "images/image", locals: { image: image,
version: version,
show_caption: show_caption }
end
def image_direct_upload_url(imageable)
direct_uploads_url("direct_upload[resource_type]": imageable.class.name,
"direct_upload[resource_id]": imageable.id,
"direct_upload[resource_relation]": "image")
end
end

View File

@@ -1,13 +1,5 @@
module OfficingHelper
def officer_assignments_select_options(officer_assignments)
options = []
officer_assignments.each do |oa|
options << ["#{oa.booth_assignment.booth.name}: #{l(oa.date.to_date, format: :long)}", oa.id]
end
options_for_select(options)
end
def booths_for_officer_select_options(officer_assignments)
options = []
officer_assignments.each do |oa|
@@ -17,10 +9,6 @@ module OfficingHelper
options_for_select(options, params[:oa])
end
def system_recount_to_compare_with_final_recount(final_recount)
final_recount.booth_assignment.voters.select {|v| v.created_at.to_date == final_recount.date}.size
end
def answer_result_value(question_id, answer_index)
return nil if params.blank?
return nil if params[:questions].blank?

View File

@@ -1,7 +0,0 @@
module PollFinalRecountsHelper
def final_recount_for_date(final_recounts, date)
final_recounts.select {|f| f.date == date}.first
end
end

View File

@@ -1,7 +1,7 @@
module PollRecountsHelper
def booth_assignment_sum_final_recounts(ba)
ba.final_recounts.any? ? ba.final_recounts.to_a.sum(&:count) : nil
def total_recounts_by_booth(booth_assignment)
booth_assignment.total_results.any? ? booth_assignment.total_results.to_a.sum(&:amount) : nil
end
end

View File

@@ -28,11 +28,6 @@ module PollsHelper
options_for_select(options, params[:d])
end
def poll_final_recount_option(poll)
final_date = poll.ends_at.to_date + 1.day
options_for_select([[I18n.t("polls.final_date"), l(final_date)]])
end
def poll_booths_select_options(poll)
options = []
poll.booths.each do |booth|

View File

@@ -12,8 +12,8 @@ module ProposalsHelper
percentage = (proposal.total_votes.to_f * 100 / Proposal.votes_needed_for_success)
case percentage
when 0 then "0%"
when 0..(0.1) then "0.1%"
when (0.1)..100 then number_to_percentage(percentage, strip_insignificant_zeros: true, precision: 1)
when 0..0.1 then "0.1%"
when 0.1..100 then number_to_percentage(percentage, strip_insignificant_zeros: true, precision: 1)
else "100%"
end
end
@@ -40,11 +40,6 @@ module ProposalsHelper
end
end
def can_create_document?(document, proposal)
can?(:create, document) && proposal.documents.size < Proposal.max_documents_allowed
end
def author_of_proposal?(proposal)
author_of?(proposal, current_user)
end

View File

@@ -19,14 +19,14 @@ module WelcomeHelper
end
end
def render_image(recommended, image_field, image_version, image_default)
image_path = calculate_image_path(recommended, image_field, image_version, image_default)
def render_recommendation_image(recommended, image_default)
image_path = calculate_image_path(recommended, image_default)
image_tag(image_path) if image_path.present?
end
def calculate_image_path(recommended, image_field, image_version, image_default)
if image_field.present? && image_version.present?
recommended.send("#{image_field}", image_version)
def calculate_image_path(recommended, image_default)
if recommended.try(:image) && recommended.image.present? && recommended.image.attachment.exists?
recommended.image.attachment.send("url", :medium)
elsif image_default.present?
image_default
end

View File

@@ -74,6 +74,8 @@ module Abilities
cannot :comment_as_moderator, [::Legislation::Question, Legislation::Annotation]
can [:create, :destroy], Document
can [:destroy], Image
can [:create, :destroy], DirectUpload
end
end
end

View File

@@ -24,6 +24,7 @@ module Abilities
can :suggest, Debate
can :suggest, Proposal
can :suggest, ActsAsTaggableOn::Tag
can [:flag, :unflag], Comment
cannot [:flag, :unflag], Comment, user_id: user.id
@@ -36,8 +37,11 @@ module Abilities
can [:create, :destroy], Follow
can [:create, :destroy, :new], Document, documentable: { author_id: user.id }
can [:new_nested, :upload, :destroy_upload], Document
can [:destroy], Document, documentable: { author_id: user.id }
can [:destroy], Image, imageable: { author_id: user.id }
can [:create, :destroy], DirectUpload
unless user.organization?
can :vote, Debate
@@ -54,6 +58,7 @@ module Abilities
can :suggest, Budget::Investment, budget: { phase: "accepting" }
can :destroy, Budget::Investment, budget: { phase: ["accepting", "reviewing"] }, author_id: user.id
can :vote, Budget::Investment, budget: { phase: "selecting" }
can [:show, :create], Budget::Ballot, budget: { phase: "balloting" }
can [:create, :destroy], Budget::Ballot::Line, budget: { phase: "balloting" }

View File

@@ -7,11 +7,11 @@ class Budget
include Reclassification
include Followable
include Communitable
include Imageable
include Documentable
documentable max_documents_allowed: 3,
max_file_size: 3.megabytes,
accepted_content_types: [ "application/pdf" ]
accepts_nested_attributes_for :documents, allow_destroy: true
acts_as_votable
acts_as_paranoid column: :hidden_at

View File

@@ -3,6 +3,7 @@ module Documentable
included do
has_many :documents, as: :documentable, dependent: :destroy
accepts_nested_attributes_for :documents, allow_destroy: true
end
module ClassMethods
@@ -15,6 +16,7 @@ module Documentable
@max_file_size = options[:max_file_size]
@accepted_content_types = options[:accepted_content_types]
end
end
end

View File

@@ -24,7 +24,7 @@ module Graphqlable
end
def graphql_type_description
(model_name.human).to_s
model_name.human.to_s
end
end

View File

@@ -0,0 +1,14 @@
# can [:update, :destroy ], Image, :imageable_id => user.id, :imageable_type => 'User'
# and add a feature like forbidden/without_role_images_spec.rb to test it
module Imageable
extend ActiveSupport::Concern
included do
has_one :image, as: :imageable, dependent: :destroy
accepts_nested_attributes_for :image, allow_destroy: true, update_only: true
def image_url(style)
image.attachment.url(style) if image && image.attachment.exists?
end
end
end

View File

@@ -55,10 +55,9 @@ module Verification
end
def user_type
case
when level_three_verified?
if level_three_verified?
:level_3_user
when level_two_verified?
elsif level_two_verified?
:level_2_user
else
:level_1_user

View File

@@ -95,7 +95,7 @@ class Debate < ActiveRecord::Base
def register_vote(user, vote_value)
if votable_by?(user)
Debate.increment_counter(:cached_anonymous_votes_total, id) if (user.unverified? && !user.voted_for?(self))
Debate.increment_counter(:cached_anonymous_votes_total, id) if user.unverified? && !user.voted_for?(self)
vote_by(voter: user, vote: vote_value)
end
end

View File

@@ -0,0 +1,62 @@
class DirectUpload
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :resource, :resource_type, :resource_id,
:relation, :resource_relation,
:attachment, :cached_attachment, :user
validates_presence_of :attachment, :resource_type, :resource_relation, :user
validate :parent_resource_attachment_validations,
if: -> { attachment.present? && resource_type.present? && resource_relation.present? && user.present? }
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
if @resource_type.present? && @resource_relation.present? && (@attachment.present? || @cached_attachment.present?)
@resource = @resource_type.constantize.find_or_initialize_by(id: @resource_id)
if @resource.class.reflections[@resource_relation].macro == :has_one
@relation = @resource.send("build_#{resource_relation}", relation_attributtes)
else
@relation = @resource.send(@resource_relation).build(relation_attributtes)
end
@relation.user = user
end
end
def save_attachment
@relation.attachment.save
end
def destroy_attachment
@relation.attachment.destroy
end
def persisted?
false
end
private
def parent_resource_attachment_validations
@relation.valid?
if @relation.errors.has_key? :attachment
errors[:attachment] = @relation.errors[:attachment]
end
end
def relation_attributtes
{
attachment: @attachment,
cached_attachment: @cached_attachment,
user: @user
}
end
end

View File

@@ -1,14 +1,17 @@
class Document < ActiveRecord::Base
include DocumentsHelper
include DocumentablesHelper
has_attached_file :attachment, path: ":rails_root/public/system/:class/:prefix/:style/:filename"
has_attached_file :attachment, url: "/system/:class/:prefix/:style/:hash.:extension",
hash_data: ":class/:style",
use_timestamp: false,
hash_secret: Rails.application.secrets.secret_key_base
attr_accessor :cached_attachment
belongs_to :user
belongs_to :documentable, polymorphic: true
# Disable paperclip security validation due to polymorphic configuration
# Paperclip do not allow to user Procs on valiations definition
# Paperclip do not allow to use Procs on valiations definition
do_not_validate_attachment_file_type :attachment
validate :attachment_presence
validate :validate_attachment_content_type, if: -> { attachment.present? }
@@ -18,13 +21,14 @@ class Document < ActiveRecord::Base
validates :documentable_id, presence: true, if: -> { persisted? }
validates :documentable_type, presence: true, if: -> { persisted? }
after_save :remove_cached_document, if: -> { valid? && persisted? && cached_attachment.present? }
before_save :set_attachment_from_cached_attachment, if: -> { cached_attachment.present? }
after_save :remove_cached_attachment, if: -> { cached_attachment.present? }
def set_cached_attachment_from_attachment(prefix)
def set_cached_attachment_from_attachment
self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
attachment.path
else
prefix + attachment.url
attachment.url
end
end
@@ -40,7 +44,7 @@ class Document < ActiveRecord::Base
attachment.instance.prefix(attachment, style)
end
def prefix(attachment, _style)
def prefix(attachment, style)
if !attachment.instance.persisted?
"cached_attachments/user/#{attachment.instance.user_id}"
else
@@ -50,21 +54,25 @@ class Document < ActiveRecord::Base
private
def documentable_class
documentable_type.constantize if documentable_type.present?
end
def validate_attachment_size
if documentable.present? &&
attachment_file_size > documentable.class.max_file_size
if documentable_class.present? &&
attachment_file_size > documentable_class.max_file_size
errors[:attachment] = I18n.t("documents.errors.messages.in_between",
min: "0 Bytes",
max: "#{max_file_size(documentable)} MB")
max: "#{max_file_size(documentable_class)} MB")
end
end
def validate_attachment_content_type
if documentable.present? &&
!accepted_content_types(documentable).include?(attachment_content_type)
if documentable_class &&
!accepted_content_types(documentable_class).include?(attachment_content_type)
errors[:attachment] = I18n.t("documents.errors.messages.wrong_content_type",
content_type: attachment_content_type,
accepted_content_types: humanized_accepted_content_types(documentable))
accepted_content_types: documentable_humanized_accepted_content_types(documentable_class))
end
end
@@ -74,8 +82,12 @@ class Document < ActiveRecord::Base
end
end
def remove_cached_document
File.delete(cached_attachment) if File.exist?(cached_attachment)
def remove_cached_attachment
document = Document.new(documentable: documentable,
cached_attachment: cached_attachment,
user: user)
document.set_attachment_from_cached_attachment
document.attachment.destroy
end
end

112
app/models/image.rb Normal file
View File

@@ -0,0 +1,112 @@
class Image < ActiveRecord::Base
include ImagesHelper
include ImageablesHelper
TITLE_LEGHT_RANGE = 4..80
MIN_SIZE = 475
MAX_IMAGE_SIZE = 1.megabyte
ACCEPTED_CONTENT_TYPE = %w(image/jpeg image/jpg)
has_attached_file :attachment, styles: { large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" },
url: "/system/:class/:prefix/:style/:hash.:extension",
hash_data: ":class/:style",
use_timestamp: false,
hash_secret: Rails.application.secrets.secret_key_base
attr_accessor :cached_attachment
belongs_to :user
belongs_to :imageable, polymorphic: true
# Disable paperclip security validation due to polymorphic configuration
# Paperclip do not allow to use Procs on valiations definition
do_not_validate_attachment_file_type :attachment
validate :attachment_presence
validate :validate_attachment_content_type, if: -> { attachment.present? }
validate :validate_attachment_size, if: -> { attachment.present? }
validates :title, presence: true, length: { in: TITLE_LEGHT_RANGE }
validates :user_id, presence: true
validates :imageable_id, presence: true, if: -> { persisted? }
validates :imageable_type, presence: true, if: -> { persisted? }
validate :validate_image_dimensions, if: -> { attachment.present? && attachment.dirty? }
before_save :set_attachment_from_cached_attachment, if: -> { cached_attachment.present? }
after_save :remove_cached_attachment, if: -> { cached_attachment.present? }
def set_cached_attachment_from_attachment
self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
attachment.path
else
attachment.url
end
end
def set_attachment_from_cached_attachment
self.attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
File.open(cached_attachment)
else
URI.parse(cached_attachment)
end
end
Paperclip.interpolates :prefix do |attachment, style|
attachment.instance.prefix(attachment, style)
end
def prefix(attachment, style)
if !attachment.instance.persisted?
"cached_attachments/user/#{attachment.instance.user_id}"
else
":attachment/:id_partition"
end
end
private
def imageable_class
imageable_type.constantize if imageable_type.present?
end
def validate_image_dimensions
if attachment_of_valid_content_type?
dimensions = Paperclip::Geometry.from_file(attachment.queued_for_write[:original].path)
errors.add(:attachment, :min_image_width, required_min_width: MIN_SIZE) if dimensions.width < MIN_SIZE
errors.add(:attachment, :min_image_height, required_min_height: MIN_SIZE) if dimensions.height < MIN_SIZE
end
end
def validate_attachment_size
if imageable_class &&
attachment_file_size > 1.megabytes
errors[:attachment] = I18n.t("images.errors.messages.in_between",
min: "0 Bytes",
max: "#{imageable_max_file_size} MB")
end
end
def validate_attachment_content_type
if imageable_class && !attachment_of_valid_content_type?
errors[:attachment] = I18n.t("images.errors.messages.wrong_content_type",
content_type: attachment_content_type,
accepted_content_types: imageable_humanized_accepted_content_types)
end
end
def attachment_presence
if attachment.blank? && cached_attachment.blank?
errors[:attachment] = I18n.t("errors.messages.blank")
end
end
def attachment_of_valid_content_type?
attachment.present? && imageable_accepted_content_types.include?(attachment_content_type)
end
def remove_cached_attachment
image = Image.new(imageable: imageable,
cached_attachment: cached_attachment,
user: user)
image.set_attachment_from_cached_attachment
image.attachment.destroy
end
end

View File

@@ -4,7 +4,6 @@ class Poll
belongs_to :poll
has_many :officer_assignments, class_name: "Poll::OfficerAssignment", dependent: :destroy
has_many :final_recounts, class_name: "Poll::FinalRecount", dependent: :destroy
has_many :officers, through: :officer_assignments
has_many :voters
has_many :partial_results

View File

@@ -1,19 +0,0 @@
class Poll
class FinalRecount < ActiveRecord::Base
belongs_to :booth_assignment, class_name: "Poll::BoothAssignment"
belongs_to :officer_assignment, class_name: "Poll::OfficerAssignment"
validates :booth_assignment_id, presence: true
validates :date, presence: true, uniqueness: {scope: :booth_assignment_id}
validates :count, presence: true, numericality: {only_integer: true}
before_save :update_logs
def update_logs
if count_changed? && count_was.present?
self.count_log += ":#{count_was.to_s}"
self.officer_assignment_id_log += ":#{officer_assignment_id_was.to_s}"
end
end
end
end

View File

@@ -2,8 +2,10 @@ class Poll
class OfficerAssignment < ActiveRecord::Base
belongs_to :officer
belongs_to :booth_assignment
has_many :final_recounts
has_many :partial_results
has_many :white_results
has_many :null_results
has_many :total_results
has_many :voters
validates :officer_id, presence: true

View File

@@ -10,11 +10,11 @@ class Proposal < ActiveRecord::Base
include Graphqlable
include Followable
include Communitable
include Imageable
include Documentable
documentable max_documents_allowed: 3,
max_file_size: 3.megabytes,
accepted_content_types: [ "application/pdf" ]
accepts_nested_attributes_for :documents, allow_destroy: true
include EmbedVideosHelper
acts_as_votable
@@ -101,7 +101,7 @@ class Proposal < ActiveRecord::Base
def self.search_by_code(terms)
matched_code = match_code(terms)
results = where(id: matched_code[1]) if matched_code
return results if (results.present? && results.first.code == terms)
return results if results.present? && results.first.code == terms
end
def self.match_code(terms)

View File

@@ -1,5 +1,5 @@
<div class="admin-sidebar" data-equalizer-watch>
<ul id="admin_menu" data-accordion-menu>
<ul id="admin_menu" data-accordion-menu data-multi-open="false">
<li class="section-title">
<a href="#">
@@ -60,12 +60,12 @@
<span class="icon-checkmark-circle"></span>
<strong><%= t("admin.menu.title_polls") %></strong>
</a>
<ul id="polls_menu" <%= "class=is-active" if menu_polls? && controller.class.parent == Admin::Poll::QuestionsController %>>
<ul id="polls_menu" <%= "class=is-active" if menu_polls? && controller.class.parent == Admin::Poll %>>
<li <%= "class=active" if ["polls", "officer_assignments", "booth_assignments", "recounts", "results"].include? controller_name %>>
<%= link_to t('admin.menu.polls'), admin_polls_path %>
</li>
<li <%= "class=active" if controller_name == "questions" && controller.class.parent == Admin::Poll::QuestionsController %>>
<li <%= "class=active" if current_page?(admin_questions_path) %>>
<%= link_to t("admin.menu.poll_questions"), admin_questions_path %>
</li>

View File

@@ -1,99 +1,9 @@
<%= link_to admin_settings_path, class: "button float-right" do %>
<span class="icon-settings"></span>
<%= t("admin.menu.settings") %>
<% end %>
<%= link_to admin_stats_path, class: "button float-right" do %>
<span class="icon-stats"></span>
<%= t("admin.menu.stats") %>
<% end %>
<h2 class="inline-block"><%= t("admin.dashboard.index.title") %></h2>
<p>Desde aquí puedes administrar el sistema, a través de las siguientes acciones:</p>
<div class="small-12 medium-9">
<ul class="accordion" data-accordion data-multi-expand="true" data-allow-all-closed="true">
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Temas de debate</a>
<div class="accordion-content" data-tab-content>
<p>Los temas (también llamadas tags, o etiquetas) de debate son palabras que definen los usuarios al crear debates, para catalogarlos (ej: sanidad, movilidad, arganzuela, ...). Aquí se pueden eliminar temas inapropiados, o <strong>marcarlos para ser propuestos al crear debates</strong> (cada usuario puede definir los que quiera, pero se le sugieren algunos que nos parecen útiles como catalogación por defecto; aquí se puede cambiar cuáles se sugieren)</p>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Propuestas/Debates/Comentarios ocultos</a>
<div class="accordion-content" data-tab-content>
<p>Cuando un moderador o un administrador oculta una Propuesta/Debate/Comentario aparecerá en esta lista. De esta forma los administradores pueden revisar que se ha ocultado el elemento adecuado.</p>
<ul>
<li>Al pulsar <strong>Confirmar</strong> se acepta el que se haya ocultado, se considera que se ha hecho correctamente.</li>
<li>Al pulsar <strong>Volver a mostrar</strong> se revierte la acción de ocultar y vuelve a ser una Propuesta/Debate/Comentario visible, en el caso de que se considere
que ha sido una acción errónea el haberlo ocultado.</li>
</ul>
<p>Para facilitar la gestión, arriba encontramos un <strong>filtro</strong> con las secciones: "pendientes" (los elementos sobre los que todavía no se ha pulsado "confirmar" o "volver a mostrar", que deberían ser revisados todavía), "confirmados" y "todos".</p>
<p><em>Es recomendable revisar regularmente la sección "pendientes".</em></p>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Usuarios bloqueados</a>
<div class="accordion-content" data-tab-content>
<p>Cuando un moderador o un administrador bloquea a un usuario aparecerá en esta lista. Al <strong>bloquear a un usuario, éste deja de poder utilizarlo para ninguna acción de la web</strong>. Los administradores pueden desbloquearlos pulsando el botón al lado del nombre del usuario en la lista.</p>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Organizaciones</a>
<div class="accordion-content" data-tab-content>
<p>En la web hay dos tipos de usuarios: individuales y organizaciones. Cualquier persona puede crear usuarios de un tipo o de otro en la propia web. Los usuarios de organizaciones pueden ser verificados por parte de los administradores, confirmando que quien gestiona el usuario efectivamente representa a esa organización. Una vez se haya realizado el proceso de verificación, por el proceso externo a la web que se haya definido para ello, se pulsa el botón <strong>"Verificar"</strong> para confimarlo; lo que hará que al lado del nombre de la organización aparezca una etiqueta señalando que es una organización verificada.</p>
<p>En caso de que el proceso de verificación haya sido negativo, se pulsa el botón <strong>"Rechazar"</strong>. Para editar alguno de los datos de la organización, se pulsa el botón <strong>"Editar"</strong>.</p>
<p>En caso de que el proceso de verificación haya sido negativo, se pulsa el botón <strong>"Rechazar"</strong>. Para editar alguno de los datos de la organización, se pulsa el botón <strong>"Editar"</strong>.</p>
<p>Las organizaciones que no aparecen en la lista pueden ser encontradas para actuar sobre ellas por medio del buscador en la parte superior. Para facilitar la gestión, arriba
encontramos un <strong>filtro</strong> con las secciones: "pendientes" (las organizaciones que todavía no han sido verificadas o rechazadas), "verificadas", "rechazadas" y "todas".</p>
<p><em>Es recomendable revisar regularmente la sección "pendientes".</em></p>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Cargos Públicos</a>
<div class="accordion-content" data-tab-content>
<p>En la web, los usuarios individuales pueden ser usuarios normales, o cargos públicos. Estos últimos se diferencian de los primeros únicamente en que al lado de sus nombres aparece una <strong>etiqueta que les identifica</strong>, y cambia ligeramente el estilo de sus comentarios. Esto permite que los usuarios les identifiquen más fácilmente. Al lado de cada usuario vemos la identificación que aparece en su etiqueta, y <strong>su nivel</strong> (la manera que internamente usa la web para diferenciar entre un tipo de cargos y otros). Pulsando el botón <strong>"Editar"</strong> al lado del usuario, se puede modificar su información. Los cargos públicos que no aparecen en la lista pueden ser encontrados para actuar sobre ellos por medio del buscador en la parte superior.</p>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Moderadores</a>
<div class="accordion-content" data-tab-content>
<p>Mediante el buscador de la parte superior se pueden buscar usuarios, para activarlos o desactivarlos como moderadores de la web. Los moderadores al acceder a la web con su usuario ven en la parte superior una nueva sección llamada <strong>"Moderar"</strong></p>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Actividad de moderadores</a>
<div class="accordion-content" data-tab-content>
<p>En esta sección se va guardando <strong>todas las acciones que realizan los moderadores o los administradores respecto a la moderación</strong>: ocultar/mostrar Propuestas/Debates/Comentarios y bloquear usuarios. En la columna <strong>"Acción"</strong> comprobamos si la acción corresponde con ocultar o con volver a mostrar (restaurar) elementos o con bloquear usuarios. En las demás columnas tenemos el tipo de elemento, el contenido del elemento y el moderador o administrador que ha realizado la acción. Esta sección permite que los administradores detecten comportamientos irregulares por parte de moderadores específicos y que por lo tanto puedan corregirlos.</p>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Configuración Global</a>
<div class="accordion-content" data-tab-content>
<p>Opciones generales de configuración del sistema.</p>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Estadísticas</a>
<div class="accordion-content" data-tab-content>
<p>Estadísticas generales del sistema.</p>
</div>
</li>
</ul>
<div class="float-right">
<%= link_to root_path do %>
<%= t("admin.dashboard.index.back", org: setting['org_name']) %>
<% end %>
</div>
<h2 class="title inline-block"><%= t("admin.dashboard.index.title") %></h2>
<p><%= t("admin.dashboard.index.description", org: setting['org_name']) %></p>

View File

@@ -45,25 +45,34 @@
<div class="tabs-panel" id="tab-recounts">
<h3><%= t("admin.poll_booth_assignments.show.recounts_list") %></h3>
<table id="totals">
<thead>
<tr>
<th class="text-center"><%= t("admin.poll_booth_assignments.show.count_final") %></th>
<th class="text-center"><%= t("admin.poll_booth_assignments.show.total_system") %></th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center" id="total_final"><%= total_recounts_by_booth(@booth_assignment) || '-' %></td>
<td class="text-center" id="total_system"><%= @booth_assignment.voters.count %></td>
</tr>
</tbody>
</table>
<table id="recounts_list">
<thead>
<tr>
<th><%= t("admin.poll_booth_assignments.show.date") %></th>
<th class="text-center"><%= t("admin.poll_booth_assignments.show.count_final") %></th>
<th class="text-center"><%= t("admin.poll_booth_assignments.show.count_by_system") %></th>
</tr>
</thead>
<tbody>
<% (@poll.starts_at.to_date..@poll.ends_at.to_date).each do |voting_date| %>
<% final_recount = final_recount_for_date(@booth_assignment.final_recounts, voting_date) %>
<% system_count = @voters_by_date[voting_date].present? ? @voters_by_date[voting_date].size : 0 %>
<tr id="recounting_<%= voting_date.strftime('%Y%m%d') %>">
<td><%= l voting_date %></td>
<% if final_recount.present? %>
<td class="text-center <%= 'count-error' if final_recount.count != system_count %>" title="<%= final_recount.officer_assignment.officer.name %>"><%= final_recount.count %></td>
<% else %>
<td class="text-center" title=""> - </td>
<% end %>
<td class="text-center"><%= system_count %></td>
</tr>
<% end %>

View File

@@ -27,27 +27,24 @@
<% end %>
</tbody>
</table>
<% end %>
<% final_officer_assignments = @officer_assignments.select{|oa| oa.final == true} %>
<% if final_officer_assignments.any? %>
<h3><%= t("admin.poll_officer_assignments.by_officer.final_recounts") %></h3>
<table id="final_recount_list" class="fixed">
<h3><%= t("admin.poll_officer_assignments.by_officer.total_recounts") %></h3>
<table id="total_recount_list" class="fixed">
<thead>
<tr>
<th><%= t("admin.poll_officer_assignments.by_officer.date") %></th>
<th><%= t("admin.poll_officer_assignments.by_officer.booth") %></th>
<th class="text-right"><%= t("admin.poll_officer_assignments.by_officer.final_recount") %></th>
<th class="text-right"><%= t("admin.poll_officer_assignments.by_officer.total_recount") %></th>
</tr>
</thead>
<tbody>
<% final_officer_assignments.each do |officer_assignment| %>
<tr id="final_recount_<%= officer_assignment.date.to_date.strftime('%Y%m%d') %>">
<% @officer_assignments.each do |officer_assignment| %>
<tr id="total_recount_<%= officer_assignment.date.to_date.strftime('%Y%m%d') %>">
<td><%= l(officer_assignment.date.to_date) %></td>
<td><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %></td>
<td class="text-right">
<% if officer_assignment.final_recounts.any? %>
<%= officer_assignment.final_recounts.to_a.sum(&:count) %>
<% if officer_assignment.total_results.any? %>
<%= officer_assignment.total_results.to_a.sum(&:amount) %>
<% else %>
<span>-</span>
<% end %>

View File

@@ -26,8 +26,8 @@
ckeditor: { language: I18n.locale } %>
</div>
<div class="documents small-12" data-max-documents="<%= Poll::Question.max_documents_allowed %>">
<%= render 'documents/nested_documents', documentable: @question %>
<div class="documents small-12">
<%= render 'documents/nested_documents', documentable: @question, f: f %>
</div>
<div class="small-12">

View File

@@ -12,12 +12,12 @@
<table class="fixed margin">
<thead>
<th><%= t("admin.recounts.index.table_booth_name") %></th>
<th class="text-center"><%= t("admin.recounts.index.table_final_recount") %></th>
<th class="text-center"><%= t("admin.recounts.index.table_total_recount") %></th>
<th class="text-center"><%= t("admin.recounts.index.table_system_count") %></th>
</thead>
<tbody>
<% @booth_assignments.each do |booth_assignment| %>
<% final_recount = booth_assignment_sum_final_recounts(booth_assignment) %>
<% total_recounts = total_recounts_by_booth(booth_assignment) %>
<% system_count = booth_assignment.voters.size %>
<tr id="<%= dom_id(booth_assignment) %>_recounts" class="booth_recounts">
<td>
@@ -25,9 +25,9 @@
<%= link_to booth_assignment.booth.name, admin_poll_booth_assignment_path(@poll, booth_assignment, anchor: 'tab-recounts') %>
</strong>
</td>
<td class="text-center <%= 'count-error' if final_recount.to_i != system_count %>">
<% if final_recount.present? %>
<strong><%= final_recount %></strong>
<td class="text-center <%= 'count-error' if total_recounts.to_i != system_count %>">
<% if total_recounts.present? %>
<strong><%= total_recounts %></strong>
<% else %>
<span>-</span>
<% end %>

View File

@@ -0,0 +1,10 @@
<li>
<%= link_to admin_stats_path, title: t("admin.menu.stats") do %>
<span class="icon-stats"></span>
<% end %>
</li>
<li>
<%= link_to admin_settings_path, title: t("admin.menu.settings") do %>
<span class="icon-settings"></span>
<% end %>
</li>

View File

@@ -1,4 +1,4 @@
<%= form_for(@investment, url: form_url, method: :post) do |f| %>
<%= form_for(@investment, url: form_url, method: :post, html: { multipart: true }) do |f| %>
<%= render 'shared/errors', resource: @investment %>
<div class="row">
@@ -21,8 +21,12 @@
<%= f.text_field :external_url %>
</div>
<div class="documents small-12 column" data-max-documents="<%= Budget::Investment.max_documents_allowed %>">
<%= render 'documents/nested_documents', documentable: @investment %>
<div class="images small-12 column">
<%= render 'images/nested_image', imageable: @investment, f: f %>
</div>
<div class="documents small-12 column">
<%= render 'documents/nested_documents', documentable: @investment, f: f %>
</div>
<div class="small-12 column">
@@ -49,10 +53,10 @@
label: false,
placeholder: t("budgets.investments.form.tags_placeholder"),
aria: {describedby: "tags-list-help-text"},
class: 'js-tag-list' %>
class: 'js-tag-list tag-autocomplete',
data: {js_url: suggest_tags_path} %>
</div>
<% unless current_user.manager? %>
<div class="small-12 column">

View File

@@ -2,13 +2,21 @@
<div class="panel">
<div class="row">
<div class="small-12 medium-9 column">
<div class="small-12 medium-3 large-2 column">
<% if investment.image.present? %>
<%= image_tag investment.image_url(:thumb), alt: investment.image.title %>
<% else %>
<div class="no-image"></div>
<% end %>
</div>
<div class="small-12 medium-6 large-7 column">
<div class="budget-investment-content">
<% cache [locale_and_user_status(investment), 'index', investment, investment.author] do %>
<h3><%= link_to investment.title, namespaced_budget_investment_path(investment) %></h3>
<p class="investment-project-info">
<p class="investment-project-info">
<%= l investment.created_at.to_date %>
<% if investment.author.hidden? || investment.author.erased? %>

View File

@@ -4,14 +4,14 @@
<div class="small-12 medium-9 column">
<%= back_link_to budget_investments_path(investment.budget, heading_id: investment.heading) %>
<% if can?(:create, @document) && investment.documents.size < Budget::Investment.max_documents_allowed %>
<%= link_to t("documents.upload_document"),
new_document_path(documentable_id:investment, documentable_type: investment.class.name, from: request.url),
<% if can_destroy_image?(investment) %>
<%= link_to t("images.remove_image"),
image_path(investment.image, from: request.url),
method: :delete,
class: 'button hollow float-right' %>
<% end %>
<h1><%= investment.title %></h1>
<div class="budget-investment-info">
<%= render '/shared/author_info', resource: investment %>
@@ -20,7 +20,11 @@
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<%= investment.heading.name %>
</div>
<br>
<%= render_image(investment.image, :large, true) if investment.image.present? %>
<p id="investment_code">
<%= t("budgets.investments.show.code_html", code: investment.id) %>
</p>
@@ -116,6 +120,7 @@
<%= render partial: 'shared/social_share', locals: {
share_title: t("budgets.investments.show.share"),
title: investment.title,
image_url: image_absolute_url(investment.image, :thumb),
url: budget_investment_url(budget_id: investment.budget_id, id: investment.id)
} %>

View File

@@ -26,9 +26,9 @@
<div class="comment-info">
<% if comment.as_administrator? %>
<span class="user-name"><%= t("comments.comment.admin") %> #<%= comment.administrator_id%></span>
<span class="user-name"><%= t("comments.comment.admin") %> #<%= comment.administrator_id %></span>
<% elsif comment.as_moderator? %>
<span class="user-name"><%= t("comments.comment.moderator") %> #<%= comment.moderator_id%></span>
<span class="user-name"><%= t("comments.comment.moderator") %> #<%= comment.moderator_id %></span>
<% else %>
<% if comment.user.hidden? || comment.user.erased? %>
@@ -72,7 +72,7 @@
</div>
<% if comment.children.size > 0 %>
<%= link_to "#{dom_id(comment)}", class: "js-toggle-children relative", data: {'id': "#{dom_id(comment)}"} do %>
<%= link_to "", class: "js-toggle-children relative", data: {'id': "#{dom_id(comment)}"} do %>
<span class="show-for-sr js-child-toggle" style="display: none;"><%= t("shared.show") %></span>
<span class="show-for-sr js-child-toggle"><%= t("shared.hide") %></span>
<span id="<%= dom_id(comment) %>_children_arrow" class="icon-arrow-down"></span> <%= t("comments.comment.responses", count: comment.children.size) %>

View File

@@ -22,7 +22,9 @@
<%= f.text_field :tag_list, value: @debate.tag_list.to_s,
label: false,
placeholder: t("debates.form.tags_placeholder"),
aria: {describedby: "tag-list-help-text"} %>
aria: {describedby: "tag-list-help-text"},
data: {js_url: suggest_tags_path},
class: 'tag-autocomplete'%>
</div>
<div class="small-12 column">
<% if @debate.new_record? %>

View File

@@ -0,0 +1,31 @@
<div id="<%= dom_id(f.object) %>" class="document direct-upload document-fields nested-fields">
<%= f.hidden_field :id %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.hidden_field :cached_attachment %>
<div class="small-12 column title">
<%= f.text_field :title, placeholder: t("documents.form.title_placeholder") %>
</div>
<div class="small-12 column attachment-actions">
<div class="small-9 column action-add attachment-errors document-attachment">
<%= render_attachment(f, f.object) %>
</div>
<div class="small-3 column action-remove text-right">
<%= render_destroy_document_link(f, f.object) %>
</div>
</div>
<div class="small-6 column">
<p class="file-name">
<%= document_attachment_file_name(f.object) %>
</p>
</div>
<div class="small-12 column">
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
</div>
<hr>
</div>

View File

@@ -1,6 +1,6 @@
<% if documents.any? %>
<% if documents.size == max_documents_allowed && can?(:create, Document) %>
<% if documents.size == max_documents_allowed && can?(:destroy, Document) %>
<div class="row documents-list">
<div class="small-12 column">
<div class="callout warning text-center">

View File

@@ -1,20 +0,0 @@
<%= form_for @document,
url: documents_path(
documentable_type: @document.documentable_type,
documentable_id: @document.documentable_id,
from: params[:from]
),
html: { multipart: true, class: "documentable"},
data: { direct_upload_url: upload_documents_url(documentable_type: @document.documentable_type, documentable_id: @document.documentable_id) } do |f| %>
<%= render 'shared/errors', resource: @document %>
<div class="row">
<%= render 'plain_fields', document: @document %>
<div class="actions small-12 medium-6 large-4 end column">
<%= f.submit(t("documents.form.submit_button"), class: "button expanded") %>
</div>
</div>
<% end %>

View File

@@ -1,22 +1,26 @@
<div class="documents-list">
<%= label_tag :documents, t("documents.form.title") %>
<%= f.label :documents, t("documents.form.title") %>
<p class="help-text"><%= documentables_note(documentable) %></p>
<% documentable.documents.each_with_index do |document, index| %>
<%= render 'documents/nested_fields', document: document, index: index, documentable: documentable %>
<% end %>
<div id="nested-documents" data-max-documents-allowed="<%= documentable.class.max_documents_allowed%>">
<%= f.fields_for :documents do |documents_builder| %>
<%= render 'documents/document_fields', f: documents_builder %>
<% end %>
</div>
<%= link_to_add_association t('documents.form.add_new_document'), f, :documents,
partial: "documents/document_fields",
id: "new_document_link",
class: "button hollow #{"hide" if documentable.documents.count >= documentable.class.max_documents_allowed}",
data: {
association_insertion_node: "#nested-documents",
association_insertion_method: "append"
} %>
<div id="max-documents-notice" class="max-documents-notice callout warning text-center <%= "hide" unless max_documents_allowed?(documentable) %>">
<%= t "documents.max_documents_allowed_reached_html" %>
</div>
<hr>
</div>
<% unless max_documents_allowed?(documentable) %>
<%= link_to t("documents.form.add_new_document"),
new_nested_documents_path(documentable_type: documentable.class.name, index: documentable.documents.size),
remote: true,
id: "new_document_link",
class: "button hollow" %>
<% end %>
<div class="max-documents-notice callout warning text-center <%= "hide" unless max_documents_allowed?(documentable) %>">
<%= t "documents.max_documents_allowed_reached_html" %>
</div>
<hr>

View File

@@ -1,31 +0,0 @@
<div id="<%= document_nested_field_wrapper_id(index) %>" class="document">
<%= hidden_field_tag :id,
document.id,
name: document_nested_field_name(document, index, :id),
id: document_nested_field_id(document, index, :id) if document.persisted? %>
<%= hidden_field_tag :user_id,
current_user.id,
name: document_nested_field_name(document, index, :user_id),
id: document_nested_field_id(document, index, :user_id) %>
<%= hidden_field_tag :cached_attachment,
document.cached_attachment,
name: document_nested_field_name(document, index, :cached_attachment),
id: document_nested_field_id(document, index, :cached_attachment) %>
<%= label_tag :title, t("activerecord.attributes.document.title") %>
<%= text_field_tag :title,
document.title,
name: document_nested_field_name(document, index, :title),
id: document_nested_field_id(document, index, :title),
class: "document-title" %>
<% if document.errors[:title].any? %>
<small class="error"><%= document.errors[:title].join(", ") %></small>
<% end %>
<%= render_attachment(document, index) %>
<%= render_destroy_document_link(document, index) %>
<p class="file-name"><%= document_attachment_file_name(document) %></p>
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
<hr>
</div>

View File

@@ -1,50 +0,0 @@
<div id="plain_document_fields" class="document">
<div class="small-12 column">
<%= label_tag :document_title, t("activerecord.attributes.document.title") %>
<%= text_field_tag :document_title, document.title, name: "document[title]", class: "document-title" %>
<% if document.errors.has_key?(:title) %>
<small class="error"><%= document.errors[:title].join(", ") %></small>
<% end %>
</div>
<div class="small-12 column">
<%= hidden_field_tag :cached_attachment, document.cached_attachment, name: "document[cached_attachment]" %>
<%= file_field_tag :attachment,
accept: accepted_content_types_extensions(document.documentable.class),
label: false,
class: 'document_ajax_attachment',
data: {
url: upload_documents_url(documentable_type: document.documentable_type, documentable_id: document.documentable_id),
cached_attachment_input_field: "document_cached_attachment",
multiple: false,
nested_document: false
},
id: "document_attachment",
name: "document[attachment]" %>
<% if document.cached_attachment.blank? %>
<%= label_tag :document_attachment, t("documents.form.attachment_label"), class: 'button hollow' %>
<% else %>
<%= link_to t('documents.form.delete_button'),
destroy_upload_documents_path(path: document.cached_attachment,
nested_document: false,
documentable_type: document.documentable_type,
documentable_id: document.documentable_id),
method: :delete,
remote: true,
class: "delete float-right" %>
<% end %>
<% if document.errors.has_key?(:attachment) %>
<div class="small-12 column source-option-file">
<div class="attachment-errors">
<small class="error"><%= errors_on_attachment(document) %></small>
</div>
</div>
<% end %>
<p class="file-name"><%= document_attachment_file_name(document) %></p>
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
</div>
</div>

View File

@@ -1,17 +1 @@
<% if params[:nested_document] == "true" %>
App.Documentable.destroyNestedDocument("<%= document_nested_field_wrapper_id(params[:index]) %>", "<%= j render('layouts/flash') %>")
<% new_document_link = link_to t("documents.form.add_new_document"),
new_nested_documents_path(documentable_type: @document.documentable_type, index: params[:index]),
remote: true,
id: "new_document_link",
class: "button hollow" %>
App.Documentable.updateNewDocumentButton("<%= j new_document_link %>")
<% else %>
App.Documentable.replacePlainDocument("plain_document_fields",
"<%= j render('layouts/flash') %>",
"<%= j render('plain_fields', document: @document) %>")
<% end %>
App.Documentable.removeDocument("<%= dom_id(@document) %>")

View File

@@ -1,27 +0,0 @@
<div class="document-form <%= documentable_class(@document.documentable) %> row">
<div class="small-12 medium-9 column">
<%= back_link_to params[:from] %>
<h1><%= t("documents.new.title") %></h1>
<%= render "documents/form", form_url: documents_url %>
</div>
<div class="small-12 medium-3 column">
<span class="icon-documents float-right"></span>
<h2><%= t("documents.recommendations_title") %></h2>
<ul class="recommendations">
<li>
<%= t "documents.recommendation_one_html",
max_documents_allowed: max_documents_allowed(@document.documentable) %>
</li>
<li>
<%= t "documents.recommendation_two_html",
accepted_content_types: humanized_accepted_content_types(@document.documentable) %>
</li>
<li>
<%= t "documents.recommendation_three_html",
max_file_size: max_file_size(@document.documentable) %>
</li>
</ul>
</div>
</div>

View File

@@ -1,9 +0,0 @@
<%
new_document_link = link_to t("documents.form.add_new_document"),
new_nested_documents_path(documentable_type: params[:documentable_type], index: params[:index].to_i + 1),
remote: true,
id: "new_document_link",
class: "button hollow"
%>
App.Documentable.new("<%= j render('documents/nested_fields', document: @document, index: params[:index]) %>")
App.Documentable.updateNewDocumentButton("<%= j new_document_link %>")

View File

@@ -1,12 +0,0 @@
<% if params[:nested_document] == "true" %>
App.Documentable.uploadNestedDocument("<%= document_nested_field_wrapper_id(params[:index]) %>",
"<%= j render('documents/nested_fields', document: @document, index: params[:index]) %>",
<%= @document.cached_attachment.present? %>)
<% else %>
App.Documentable.uploadPlainDocument("plain_document_fields",
"<%= j render('documents/plain_fields', document: @document) %>",
<%= @document.cached_attachment.present? %>)
<% end %>

View File

@@ -0,0 +1,18 @@
<div class="small-12 column text-center image-preview">
<figure>
<%= image_tag image.attachment.url(version),
class: image_class(image),
alt: image.title,
title: image.title %>
<% if show_caption %>
<figcaption class="text-right">
<em><%= image.title %></em>
</figcaption>
<% end %>
</figure>
<% if show_caption %>
<hr>
<% end %>
</div>

View File

@@ -0,0 +1,31 @@
<div id="<%= dom_id(f.object) %>" class="image direct-upload nested-fields">
<%= f.hidden_field :id %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.hidden_field :cached_attachment %>
<div class="small-12 column title">
<%= f.text_field :title, placeholder: t("images.form.title_placeholder") %>
</div>
<%= render_image(f.object, :thumb, false) if f.object.attachment.exists? %>
<div class="small-12 column attachment-actions">
<div class="small-9 column action-add attachment-errors image-attachment">
<%= render_image_attachment(f, imageable, f.object) %>
</div>
<div class="small-3 column action-remove text-right">
<%= render_destroy_image_link(f, f.object) %>
</div>
</div>
<div class="small-6 column">
<p class="file-name">
<%= image_attachment_file_name(f.object) %>
</p>
</div>
<div class="small-12 column">
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
<div>
<%= f.label :image, t("images.form.title") %>
<p class="help-text"><%= imageables_note(imageable) %></p>
<div id="nested-image">
<%= f.fields_for :image do |image_builder| %>
<%= render 'images/image_fields', f: image_builder, imageable: imageable %>
<% end %>
</div>
</div>
<%= link_to_add_association t('images.form.add_new_image'), f, :image,
force_non_association_create: true,
partial: "images/image_fields",
id: "new_image_link",
class: "button hollow #{"hide" if imageable.image.present?}",
render_options: {
locals: { imageable: imageable }
},
data: {
association_insertion_node: "#nested-image",
association_insertion_method: "append"
} %>
<hr>

View File

@@ -0,0 +1 @@
App.Imageable.removeImage("<%= dom_id(@image) %>")

View File

@@ -2,34 +2,31 @@
<div class="top-links">
<div class="expanded row">
<%= render 'shared/locale_switcher' %>
<%= link_to t("admin.dashboard.index.back") + " " + setting['org_name'],
root_path, class: "float-right back-web" %>
</div>
</div>
<div class="expanded row">
<div class="top-bar">
<%= link_to setting['org_name'], namespaced_root_path, class: "logo show-for-small-only" %>
<span data-responsive-toggle="responsive-menu" data-hide-for="medium" class="float-right">
<span class="menu-icon dark" data-toggle></span>
<%= t("application.menu")%>
</span>
<div id="responsive-menu">
<div class="top-bar-title">
<h1>
<%= link_to namespaced_root_path, class: "hide-for-small-only" do %>
<%= image_tag(image_path_for('logo_header.png'), class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %>
<%= link_to namespaced_root_path do %>
<%= setting['org_name'] %>
&nbsp;|&nbsp;<%= namespaced_header_title %>
<br><small><%= namespaced_header_title %></small>
<% end %>
</h1>
</div>
<div id="responsive-menu">
<div class="top-bar-right">
<ul class="dropdown menu" data-dropdown-menu>
<%= render "admin/shared/admin_shortcuts" %>
<%= render "shared/admin_login_items" %>
<%= render "devise/menu/login_items" %>
</ul>

View File

@@ -30,7 +30,7 @@
<div class="off-canvas-content" data-off-canvas-content>
<%= render 'layouts/admin_header' %>
<div class="no-margin-top row no-max-width collapse" data-equalizer>
<div class="no-margin-top row expanded collapse" data-equalizer>
<div class="small-12 medium-3 column" data-equalizer-watch>
<div class="show-for-small-only">

View File

@@ -45,7 +45,7 @@
</div>
</header>
<main class="no-margin-top row no-max-width collapse">
<main class="no-margin-top row expanded collapse">
<div class="small-12 medium-3 column">
<%= render "/management/menu" %>
</div>

View File

@@ -15,7 +15,7 @@
<%= submit_tag t('.select_version_submit'), class: "button" %>
<% end %>
<% if @draft_version.changelog.present? %>
<span><%= link_to t('.see_changes'), legislation_process_draft_version_changes_path(@process, @draft_version) %></span>
<span><%= link_to t('.see_changes'), legislation_process_draft_version_changes_path(@process, @draft_version), class: "see-changes" %></span>
<% end %>
</div>
<span><%= t('.updated_at', date: format_date(@draft_version.updated_at)) %></span>

Some files were not shown because too many files have changed in this diff Show More