diff --git a/.rubocop.yml b/.rubocop.yml
index e2cc2bf93..49de3550b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,6 +1,8 @@
inherit_from: .rubocop_todo.yml
AllCops:
+ DisplayCopNames: true
+ DisplayStyleGuide: true
Include:
- '**/Rakefile'
- '**/config.ru'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 4704696e5..f8d4bb811 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -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
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 2ff87100a..0b3ee0201 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -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);
diff --git a/app/assets/javascripts/documentable.js.coffee b/app/assets/javascripts/documentable.js.coffee
index 8683ce5e2..7cc71f39d 100644
--- a/app/assets/javascripts/documentable.js.coffee
+++ b/app/assets/javascripts/documentable.js.coffee
@@ -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('
')
- $(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 = '' + data.jqXHR.responseJSON.errors + ''
+ $(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')
diff --git a/app/assets/javascripts/flaggable.js.coffee b/app/assets/javascripts/flaggable.js.coffee
new file mode 100644
index 000000000..7ada75686
--- /dev/null
+++ b/app/assets/javascripts/flaggable.js.coffee
@@ -0,0 +1,4 @@
+App.Flaggable =
+
+ update: (resource_id, button) ->
+ $("#" + resource_id + " .js-flag-actions").html(button).foundation()
diff --git a/app/assets/javascripts/imageable.js.coffee b/app/assets/javascripts/imageable.js.coffee
new file mode 100644
index 000000000..e029dff52
--- /dev/null
+++ b/app/assets/javascripts/imageable.js.coffee
@@ -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 = '' + data.jqXHR.responseJSON.errors + ''
+ $(data.errorContainer).append(errors)
+
+ setPreview: (data) ->
+ image_preview = ''
+ 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')
diff --git a/app/assets/javascripts/tag_autocomplete.js.coffee b/app/assets/javascripts/tag_autocomplete.js.coffee
new file mode 100644
index 000000000..be27cd81c
--- /dev/null
+++ b/app/assets/javascripts/tag_autocomplete.js.coffee
@@ -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();
\ No newline at end of file
diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss
index 6ac728e2c..3dcf4a474 100644
--- a/app/assets/stylesheets/admin.scss
+++ b/app/assets/stylesheets/admin.scss
@@ -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;
}
}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 19c73de32..361dcfde3 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -16,4 +16,5 @@
@import 'annotator_overrides';
@import 'jquery-ui/datepicker';
@import 'datepicker_overrides';
-@import 'documentable';
+@import 'jquery-ui/autocomplete';
+@import 'autocomplete_overrides';
diff --git a/app/assets/stylesheets/autocomplete_overrides.scss b/app/assets/stylesheets/autocomplete_overrides.scss
new file mode 100644
index 000000000..dd2b939ad
--- /dev/null
+++ b/app/assets/stylesheets/autocomplete_overrides.scss
@@ -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);
+ }
+ }
+
+}
diff --git a/app/assets/stylesheets/community.scss b/app/assets/stylesheets/community.scss
index 0dddf4cdb..c06d5c5d2 100644
--- a/app/assets/stylesheets/community.scss
+++ b/app/assets/stylesheets/community.scss
@@ -1,5 +1,6 @@
.communities-show {
- .button.disabled, .button[disabled] {
+ .button.disabled,
+ .button[disabled] {
pointer-events: none;
}
diff --git a/app/assets/stylesheets/datepicker_overrides.scss b/app/assets/stylesheets/datepicker_overrides.scss
index 79414ad99..190784d15 100644
--- a/app/assets/stylesheets/datepicker_overrides.scss
+++ b/app/assets/stylesheets/datepicker_overrides.scss
@@ -44,18 +44,18 @@
position: absolute;
top: 4px;
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 {
diff --git a/app/assets/stylesheets/documentable.scss b/app/assets/stylesheets/documentable.scss
deleted file mode 100644
index 2aa015a14..000000000
--- a/app/assets/stylesheets/documentable.scss
+++ /dev/null
@@ -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;
- }
-}
diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss
index 5e92ee460..e55832667 100644
--- a/app/assets/stylesheets/icons.scss
+++ b/app/assets/stylesheets/icons.scss
@@ -197,6 +197,10 @@
content: '\53';
}
+.icon-image::before {
+ content: '\68';
+}
+
.icon-notification::before {
content: '\6e';
}
diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss
index 9d7b31a12..d05dbd54b 100644
--- a/app/assets/stylesheets/layout.scss
+++ b/app/assets/stylesheets/layout.scss
@@ -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 {
diff --git a/app/assets/stylesheets/legislation_process.scss b/app/assets/stylesheets/legislation_process.scss
index 2ec9f8ee9..0e73321d3 100644
--- a/app/assets/stylesheets/legislation_process.scss
+++ b/app/assets/stylesheets/legislation_process.scss
@@ -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 {
diff --git a/app/assets/stylesheets/mixins.scss b/app/assets/stylesheets/mixins.scss
index 70f4c179d..0028683c2 100644
--- a/app/assets/stylesheets/mixins.scss
+++ b/app/assets/stylesheets/mixins.scss
@@ -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;
+ }
+
+}
diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss
index 8c6fc5415..fbcda0ebb 100644
--- a/app/assets/stylesheets/participation.scss
+++ b/app/assets/stylesheets/participation.scss
@@ -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;
diff --git a/app/controllers/admin/poll/base_controller.rb b/app/controllers/admin/poll/base_controller.rb
new file mode 100644
index 000000000..c58e4f23c
--- /dev/null
+++ b/app/controllers/admin/poll/base_controller.rb
@@ -0,0 +1,10 @@
+class Admin::Poll::BaseController < Admin::BaseController
+ helper_method :namespace
+
+ private
+
+ def namespace
+ "admin"
+ end
+
+end
diff --git a/app/controllers/admin/poll/booth_assignments_controller.rb b/app/controllers/admin/poll/booth_assignments_controller.rb
index 358f26a65..06eb233bd 100644
--- a/app/controllers/admin/poll/booth_assignments_controller.rb
+++ b/app/controllers/admin/poll/booth_assignments_controller.rb
@@ -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
diff --git a/app/controllers/admin/poll/booths_controller.rb b/app/controllers/admin/poll/booths_controller.rb
index 4b322f0b2..7a3a1370d 100644
--- a/app/controllers/admin/poll/booths_controller.rb
+++ b/app/controllers/admin/poll/booths_controller.rb
@@ -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
@@ -41,4 +41,4 @@ class Admin::Poll::BoothsController < Admin::BaseController
params.require(:poll_booth).permit(:name, :location)
end
-end
\ No newline at end of file
+end
diff --git a/app/controllers/admin/poll/officer_assignments_controller.rb b/app/controllers/admin/poll/officer_assignments_controller.rb
index 45c9a225a..7fa120aa6 100644
--- a/app/controllers/admin/poll/officer_assignments_controller.rb
+++ b/app/controllers/admin/poll/officer_assignments_controller.rb
@@ -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
diff --git a/app/controllers/admin/poll/officers_controller.rb b/app/controllers/admin/poll/officers_controller.rb
index 2641a12b5..1d5c19634 100644
--- a/app/controllers/admin/poll/officers_controller.rb
+++ b/app/controllers/admin/poll/officers_controller.rb
@@ -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
@@ -36,4 +36,4 @@ class Admin::Poll::OfficersController < Admin::BaseController
def edit
end
-end
\ No newline at end of file
+end
diff --git a/app/controllers/admin/poll/polls_controller.rb b/app/controllers/admin/poll/polls_controller.rb
index cad91f218..c95c8ed1f 100644
--- a/app/controllers/admin/poll/polls_controller.rb
+++ b/app/controllers/admin/poll/polls_controller.rb
@@ -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]
diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb
index 3bbe1c395..97b301d43 100644
--- a/app/controllers/admin/poll/questions_controller.rb
+++ b/app/controllers/admin/poll/questions_controller.rb
@@ -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
diff --git a/app/controllers/admin/poll/recounts_controller.rb b/app/controllers/admin/poll/recounts_controller.rb
index 57289a207..6d4a9a442 100644
--- a/app/controllers/admin/poll/recounts_controller.rb
+++ b/app/controllers/admin/poll/recounts_controller.rb
@@ -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
diff --git a/app/controllers/admin/poll/results_controller.rb b/app/controllers/admin/poll/results_controller.rb
index 2c5bbba27..8d1d22d91 100644
--- a/app/controllers/admin/poll/results_controller.rb
+++ b/app/controllers/admin/poll/results_controller.rb
@@ -1,4 +1,4 @@
-class Admin::Poll::ResultsController < Admin::BaseController
+class Admin::Poll::ResultsController < Admin::Poll::BaseController
before_action :load_poll
def index
@@ -10,4 +10,4 @@ class Admin::Poll::ResultsController < Admin::BaseController
def load_poll
@poll = ::Poll.includes(:questions).find(params[:poll_id])
end
-end
\ No newline at end of file
+end
diff --git a/app/controllers/admin/poll/shifts_controller.rb b/app/controllers/admin/poll/shifts_controller.rb
index 4294045e5..48f540c07 100644
--- a/app/controllers/admin/poll/shifts_controller.rb
+++ b/app/controllers/admin/poll/shifts_controller.rb
@@ -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
@@ -57,4 +57,4 @@ class Admin::Poll::ShiftsController < Admin::BaseController
params.require(:shift).permit(:booth_id, :officer_id, :date)
end
-end
\ No newline at end of file
+end
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index 63ec8cf52..70541a3a0 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -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']
diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb
index 799d1f76d..fb82c51db 100644
--- a/app/controllers/budgets/investments_controller.rb
+++ b/app/controllers/budgets/investments_controller.rb
@@ -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
diff --git a/app/controllers/concerns/commentable_actions.rb b/app/controllers/concerns/commentable_actions.rb
index e0a2d7719..826d90da9 100644
--- a/app/controllers/concerns/commentable_actions.rb
+++ b/app/controllers/concerns/commentable_actions.rb
@@ -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
diff --git a/app/controllers/direct_uploads_controller.rb b/app/controllers/direct_uploads_controller.rb
new file mode 100644
index 000000000..ade7d631f
--- /dev/null
+++ b/app/controllers/direct_uploads_controller.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb
index 8f085e27f..001d23446 100644
--- a/app/controllers/documents_controller.rb
+++ b/app/controllers/documents_controller.rb
@@ -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
diff --git a/app/controllers/images_controller.rb b/app/controllers/images_controller.rb
new file mode 100644
index 000000000..e273234f2
--- /dev/null
+++ b/app/controllers/images_controller.rb
@@ -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
diff --git a/app/controllers/legislation/annotations_controller.rb b/app/controllers/legislation/annotations_controller.rb
index 352fce266..ebbe05b12 100644
--- a/app/controllers/legislation/annotations_controller.rb
+++ b/app/controllers/legislation/annotations_controller.rb
@@ -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(
diff --git a/app/controllers/legislation/answers_controller.rb b/app/controllers/legislation/answers_controller.rb
index 2e7e058e7..ecaa0037a 100644
--- a/app/controllers/legislation/answers_controller.rb
+++ b/app/controllers/legislation/answers_controller.rb
@@ -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
diff --git a/app/controllers/management/document_verifications_controller.rb b/app/controllers/management/document_verifications_controller.rb
index 687d8ee52..f59c00c8f 100644
--- a/app/controllers/management/document_verifications_controller.rb
+++ b/app/controllers/management/document_verifications_controller.rb
@@ -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
diff --git a/app/controllers/officing/polls_controller.rb b/app/controllers/officing/polls_controller.rb
index 46bcf9f37..5ba94e024 100644
--- a/app/controllers/officing/polls_controller.rb
+++ b/app/controllers/officing/polls_controller.rb
@@ -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
diff --git a/app/controllers/officing/results_controller.rb b/app/controllers/officing/results_controller.rb
index 65a6deac5..3ad7a91ea 100644
--- a/app/controllers/officing/results_controller.rb
+++ b/app/controllers/officing/results_controller.rb
@@ -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
diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb
index bb1560f54..1849dff97 100644
--- a/app/controllers/polls/questions_controller.rb
+++ b/app/controllers/polls/questions_controller.rb
@@ -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
diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb
index 069445bc9..e33b2357f 100644
--- a/app/controllers/proposals_controller.rb
+++ b/app/controllers/proposals_controller.rb
@@ -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
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
new file mode 100644
index 000000000..c58067048
--- /dev/null
+++ b/app/controllers/tags_controller.rb
@@ -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
diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb
index f9c2e8c69..2cefcdf27 100644
--- a/app/helpers/admin_helper.rb
+++ b/app/helpers/admin_helper.rb
@@ -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?
@@ -39,7 +39,7 @@ module AdminHelper
def menu_customization?
["pages", "images", "content_blocks"].include? controller_name
end
-
+
def official_level_options
options = [["", 0]]
(1..5).each do |i|
diff --git a/app/helpers/direct_uploads_helper.rb b/app/helpers/direct_uploads_helper.rb
new file mode 100644
index 000000000..87ed630d7
--- /dev/null
+++ b/app/helpers/direct_uploads_helper.rb
@@ -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
\ No newline at end of file
diff --git a/app/helpers/documentables_helper.rb b/app/helpers/documentables_helper.rb
index edbf88131..8d9f85578 100644
--- a/app/helpers/documentables_helper.rb
+++ b/app/helpers/documentables_helper.rb
@@ -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)
diff --git a/app/helpers/documents_helper.rb b/app/helpers/documents_helper.rb
index 72ebbf021..d7e8d1dea 100644
--- a/app/helpers/documents_helper.rb
+++ b/app/helpers/documents_helper.rb
@@ -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
diff --git a/app/helpers/imageables_helper.rb b/app/helpers/imageables_helper.rb
new file mode 100644
index 000000000..b1c8059ce
--- /dev/null
+++ b/app/helpers/imageables_helper.rb
@@ -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
\ No newline at end of file
diff --git a/app/helpers/images_helper.rb b/app/helpers/images_helper.rb
new file mode 100644
index 000000000..4e2bdff0e
--- /dev/null
+++ b/app/helpers/images_helper.rb
@@ -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
diff --git a/app/helpers/officing_helper.rb b/app/helpers/officing_helper.rb
index 4e824b3f2..3d53acf6d 100644
--- a/app/helpers/officing_helper.rb
+++ b/app/helpers/officing_helper.rb
@@ -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?
diff --git a/app/helpers/poll_final_recounts_helper.rb b/app/helpers/poll_final_recounts_helper.rb
deleted file mode 100644
index e196cb65b..000000000
--- a/app/helpers/poll_final_recounts_helper.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module PollFinalRecountsHelper
-
- def final_recount_for_date(final_recounts, date)
- final_recounts.select {|f| f.date == date}.first
- end
-
-end
\ No newline at end of file
diff --git a/app/helpers/poll_recounts_helper.rb b/app/helpers/poll_recounts_helper.rb
index 95ea813e3..716b71d85 100644
--- a/app/helpers/poll_recounts_helper.rb
+++ b/app/helpers/poll_recounts_helper.rb
@@ -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
diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb
index 169599a7c..27d33ea04 100644
--- a/app/helpers/polls_helper.rb
+++ b/app/helpers/polls_helper.rb
@@ -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|
@@ -46,4 +41,4 @@ module PollsHelper
booth.name + location
end
-end
\ No newline at end of file
+end
diff --git a/app/helpers/proposals_helper.rb b/app/helpers/proposals_helper.rb
index 6ef3be75d..9d5ac40cc 100644
--- a/app/helpers/proposals_helper.rb
+++ b/app/helpers/proposals_helper.rb
@@ -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
diff --git a/app/helpers/welcome_helper.rb b/app/helpers/welcome_helper.rb
index 76ae62d81..9f8c8c13e 100644
--- a/app/helpers/welcome_helper.rb
+++ b/app/helpers/welcome_helper.rb
@@ -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
diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb
index 773dabf34..4278d4f8d 100644
--- a/app/models/abilities/administrator.rb
+++ b/app/models/abilities/administrator.rb
@@ -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
diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb
index 620cfb212..e4e92abaf 100644
--- a/app/models/abilities/common.rb
+++ b/app/models/abilities/common.rb
@@ -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" }
diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb
index d658e7ab2..857025375 100644
--- a/app/models/budget/investment.rb
+++ b/app/models/budget/investment.rb
@@ -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
diff --git a/app/models/concerns/documentable.rb b/app/models/concerns/documentable.rb
index 729a0b0f8..36e91ece7 100644
--- a/app/models/concerns/documentable.rb
+++ b/app/models/concerns/documentable.rb
@@ -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
diff --git a/app/models/concerns/graphqlable.rb b/app/models/concerns/graphqlable.rb
index 228810579..c8b47cece 100644
--- a/app/models/concerns/graphqlable.rb
+++ b/app/models/concerns/graphqlable.rb
@@ -24,7 +24,7 @@ module Graphqlable
end
def graphql_type_description
- (model_name.human).to_s
+ model_name.human.to_s
end
end
diff --git a/app/models/concerns/imageable.rb b/app/models/concerns/imageable.rb
new file mode 100644
index 000000000..a2a2f537d
--- /dev/null
+++ b/app/models/concerns/imageable.rb
@@ -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
diff --git a/app/models/concerns/verification.rb b/app/models/concerns/verification.rb
index a4770117e..b8d23f594 100644
--- a/app/models/concerns/verification.rb
+++ b/app/models/concerns/verification.rb
@@ -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
diff --git a/app/models/debate.rb b/app/models/debate.rb
index 3488b0519..eb424c308 100644
--- a/app/models/debate.rb
+++ b/app/models/debate.rb
@@ -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
diff --git a/app/models/direct_upload.rb b/app/models/direct_upload.rb
new file mode 100644
index 000000000..a6b6ef276
--- /dev/null
+++ b/app/models/direct_upload.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/document.rb b/app/models/document.rb
index 3556d4c0a..f680bc04e 100644
--- a/app/models/document.rb
+++ b/app/models/document.rb
@@ -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
diff --git a/app/models/image.rb b/app/models/image.rb
new file mode 100644
index 000000000..84b38dd4a
--- /dev/null
+++ b/app/models/image.rb
@@ -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
diff --git a/app/models/poll/booth_assignment.rb b/app/models/poll/booth_assignment.rb
index 47cb01bfe..8b4b655ef 100644
--- a/app/models/poll/booth_assignment.rb
+++ b/app/models/poll/booth_assignment.rb
@@ -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
diff --git a/app/models/poll/final_recount.rb b/app/models/poll/final_recount.rb
deleted file mode 100644
index 3647c85ca..000000000
--- a/app/models/poll/final_recount.rb
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/app/models/poll/officer_assignment.rb b/app/models/poll/officer_assignment.rb
index 1d7326500..8e86d309c 100644
--- a/app/models/poll/officer_assignment.rb
+++ b/app/models/poll/officer_assignment.rb
@@ -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
diff --git a/app/models/proposal.rb b/app/models/proposal.rb
index e3c7a8c57..344232e26 100644
--- a/app/models/proposal.rb
+++ b/app/models/proposal.rb
@@ -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
+ accepted_content_types: [ "application/pdf" ]
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)
diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb
index 11d58adc8..bb246cb6c 100644
--- a/app/views/admin/_menu.html.erb
+++ b/app/views/admin/_menu.html.erb
@@ -1,5 +1,5 @@