Merge branch 'master' into 1856-legislation_processes_proposals_phase
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
AllCops:
|
||||
DisplayCopNames: true
|
||||
DisplayStyleGuide: true
|
||||
Include:
|
||||
- '**/Rakefile'
|
||||
- '**/config.ru'
|
||||
|
||||
@@ -454,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.
|
||||
@@ -480,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
|
||||
@@ -527,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:
|
||||
@@ -572,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.
|
||||
@@ -625,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_
|
||||
@@ -668,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.
|
||||
@@ -698,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
|
||||
|
||||
5
Gemfile
5
Gemfile
@@ -53,10 +53,13 @@ gem 'turnout', '~> 2.4.0'
|
||||
gem 'uglifier', '~> 3.2.0'
|
||||
gem 'unicorn', '~> 5.3.0'
|
||||
gem 'whenever', '~> 0.9.7', require: false
|
||||
source 'https://rails-assets.org' do
|
||||
gem 'rails-assets-leaflet'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
gem "bullet", '~> 5.5.1'
|
||||
gem 'byebug', '~> 9.0.6'
|
||||
gem 'byebug', '~> 9.1.0'
|
||||
gem 'factory_girl_rails', '~> 4.8.0'
|
||||
gem "faker", '~> 1.7.3'
|
||||
gem 'i18n-tasks', '~> 0.9.15'
|
||||
|
||||
12
Gemfile.lock
12
Gemfile.lock
@@ -71,7 +71,7 @@ GEM
|
||||
bullet (5.5.1)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.10.0)
|
||||
byebug (9.0.6)
|
||||
byebug (9.1.0)
|
||||
cancancan (1.16.0)
|
||||
capistrano (3.8.2)
|
||||
airbrussh (>= 1.0.0)
|
||||
@@ -251,7 +251,7 @@ GEM
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mimemagic (0.3.2)
|
||||
mini_portile2 (2.2.0)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.10.3)
|
||||
mixlib-cli (1.7.0)
|
||||
mixlib-config (2.2.4)
|
||||
@@ -262,8 +262,8 @@ GEM
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (4.1.0)
|
||||
newrelic_rpm (4.1.0.333)
|
||||
nokogiri (1.8.0)
|
||||
mini_portile2 (~> 2.2.0)
|
||||
nokogiri (1.8.1)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
nori (2.6.0)
|
||||
oauth (0.5.3)
|
||||
oauth2 (1.4.0)
|
||||
@@ -334,6 +334,7 @@ GEM
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.9)
|
||||
sprockets-rails
|
||||
rails-assets-leaflet (1.1.0)
|
||||
rails-assets-markdown-it (8.2.2)
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
@@ -493,7 +494,7 @@ DEPENDENCIES
|
||||
ancestry (~> 2.2.2)
|
||||
browser (~> 2.3.0)
|
||||
bullet (~> 5.5.1)
|
||||
byebug (~> 9.0.6)
|
||||
byebug (~> 9.1.0)
|
||||
cancancan (~> 1.16.0)
|
||||
capistrano (~> 3.8.1)
|
||||
capistrano-bundler (~> 1.2)
|
||||
@@ -542,6 +543,7 @@ DEPENDENCIES
|
||||
poltergeist (~> 1.15.0)
|
||||
quiet_assets (~> 1.1.0)
|
||||
rails (= 4.2.9)
|
||||
rails-assets-leaflet!
|
||||
rails-assets-markdown-it (~> 8.2.1)!
|
||||
redcarpet (~> 3.4.0)
|
||||
responders (~> 2.4.0)
|
||||
|
||||
Binary file not shown.
@@ -62,4 +62,7 @@
|
||||
<glyph glyph-name="expand" unicode="0" d="M26 168l-26-158c0-2 1-5 3-7 0 0 0 0 0 0 2-2 5-3 7-3l158 27c3 0 6 3 7 6 1 3 0 7-3 9l-30 31 82 82c4 4 4 9 0 13l-57 57c-3 3-9 3-12 0l-83-83-31 31c-2 2-5 3-9 2-3-1-5-4-6-7z m460 176l26 158c0 2-1 5-3 7 0 0 0 0 0 0-2 2-5 3-7 3l-158-27c-3 0-6-3-7-6-1-3 0-7 3-9l30-31-82-82c-4-4-4-9 0-13l57-57c3-3 9-3 12 0l83 83 31-31c2-2 5-3 9-2 3 1 5 4 6 7z"/>
|
||||
<glyph glyph-name="telegram" unicode="1" d="M504 509c6-5 9-11 8-18l-73-439c-1-6-4-10-10-13-2-2-5-2-8-2-3 0-5 0-7 1l-130 53-69-84c-3-5-8-7-14-7-2 0-4 0-6 1-4 1-7 4-9 7-2 3-3 6-3 10l0 100 247 303-306-265-113 47c-7 2-10 7-11 15 0 8 3 14 9 17l476 274c2 2 5 3 9 3 4 0 7-1 10-3z"/>
|
||||
<glyph glyph-name="instagram" unicode="2" d="M426 105l0 185-39 0c4-12 6-25 6-38 0-24-6-46-18-66-13-20-29-36-50-48-21-12-44-18-69-18-37 0-69 13-96 39-27 26-40 57-40 93 0 13 2 26 6 38l-41 0 0-185c0-5 2-10 5-13 4-3 8-5 13-5l305 0c5 0 9 2 13 5 3 3 5 8 5 13z m-81 152c0 23-9 44-26 60-18 17-38 25-63 25-24 0-45-8-62-25-17-16-26-37-26-60 0-24 9-44 26-61 17-16 38-25 62-25 25 0 45 9 63 25 17 17 26 37 26 61z m81 103l0 47c0 5-2 10-6 14-4 4-8 6-14 6l-50 0c-5 0-10-2-14-6-4-4-5-9-5-14l0-47c0-6 1-10 5-14 4-4 9-6 14-6l50 0c6 0 10 2 14 6 4 4 6 8 6 14z m49 59l0-326c0-16-5-29-16-40-11-11-24-16-40-16l-326 0c-16 0-29 5-40 16-11 11-16 24-16 40l0 326c0 16 5 29 16 40 11 11 24 16 40 16l326 0c16 0 29-5 40-16 11-11 16-24 16-40z"/>
|
||||
<glyph glyph-name="image" unicode="3" d="M165 347c0-15-6-28-16-38-11-11-24-16-39-16-16 0-28 5-39 16-11 10-16 23-16 38 0 16 5 29 16 39 11 11 23 16 39 16 15 0 28-5 39-16 10-10 16-23 16-39z m292-109l0-128-402 0 0 55 91 91 46-46 146 147z m28 201l-458 0c-2 0-4-1-6-3-2-2-3-4-3-6l0-348c0-2 1-4 3-6 2-2 4-3 6-3l458 0c2 0 4 1 6 3 2 2 3 4 3 6l0 348c0 2-1 4-3 6-2 2-4 3-6 3z m45-9l0-348c0-12-4-23-13-32-9-9-20-13-32-13l-458 0c-12 0-23 4-32 13-9 9-13 20-13 32l0 348c0 12 4 23 13 32 9 9 20 13 32 13l458 0c12 0 23-4 32-13 9-9 13-20 13-32z"/>
|
||||
<glyph glyph-name="search-plus" unicode="4" d="M311 283l0-18c0-2-1-4-3-6-2-2-4-3-6-3l-64 0 0-64c0-2-1-5-3-6-2-2-4-3-6-3l-19 0c-2 0-4 1-6 3-2 1-3 4-3 6l0 64-64 0c-2 0-4 1-6 3-2 2-3 4-3 6l0 18c0 3 1 5 3 7 2 2 4 3 6 3l64 0 0 64c0 2 1 4 3 6 2 2 4 3 6 3l19 0c2 0 4-1 6-3 2-2 3-4 3-6l0-64 64 0c2 0 4-1 6-3 2-2 3-4 3-7z m36-9c0 36-12 66-37 91-25 25-55 37-91 37-35 0-65-12-90-37-25-25-38-55-38-91 0-35 13-65 38-90 25-25 55-38 90-38 36 0 66 13 91 38 25 25 37 55 37 90z m147-237c0-11-4-19-11-26-7-7-16-11-26-11-10 0-19 4-26 11l-98 98c-34-24-72-36-114-36-27 0-53 5-78 16-25 11-46 25-64 43-18 18-32 39-43 64-10 25-16 51-16 78 0 28 6 54 16 78 11 25 25 47 43 65 18 18 39 32 64 43 25 10 51 15 78 15 28 0 54-5 79-15 24-11 46-25 64-43 18-18 32-40 43-65 10-24 16-50 16-78 0-42-12-80-36-114l98-98c7-7 11-15 11-25z"/>
|
||||
<glyph glyph-name="search-minus" unicode="5" d="M311 283l0-18c0-2-1-4-3-6-2-2-4-3-6-3l-165 0c-2 0-4 1-6 3-2 2-3 4-3 6l0 18c0 3 1 5 3 7 2 2 4 3 6 3l165 0c2 0 4-1 6-3 2-2 3-4 3-7z m36-9c0 36-12 66-37 91-25 25-55 37-91 37-35 0-65-12-90-37-25-25-38-55-38-91 0-35 13-65 38-90 25-25 55-38 90-38 36 0 66 13 91 38 25 25 37 55 37 90z m147-237c0-11-4-19-11-26-7-7-16-11-26-11-10 0-19 4-26 11l-98 98c-34-24-72-36-114-36-27 0-53 5-78 16-25 11-46 25-64 43-18 18-32 39-43 64-10 25-16 51-16 78 0 28 6 54 16 78 11 25 25 47 43 65 18 18 39 32 64 43 25 10 51 15 78 15 28 0 54-5 79-15 24-11 46-25 64-43 18-18 32-40 43-65 10-24 16-50 16-78 0-42-12-80-36-114l98-98c7-7 11-15 11-25z"/>
|
||||
</font></defs></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
Binary file not shown.
Binary file not shown.
BIN
app/assets/images/custom/example_horizontal.jpg
Normal file
BIN
app/assets/images/custom/example_horizontal.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
BIN
app/assets/images/custom/example_vertical.jpg
Normal file
BIN
app/assets/images/custom/example_vertical.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
@@ -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
|
||||
@@ -62,8 +63,14 @@
|
||||
//= require followable
|
||||
//= require flaggable
|
||||
//= require documentable
|
||||
//= require imageable
|
||||
//= require tree_navigator
|
||||
//= require custom
|
||||
//= require tag_autocomplete
|
||||
//= require polls_admin
|
||||
//= require leaflet
|
||||
//= require map
|
||||
//= require polls
|
||||
|
||||
var initialize_modules = function() {
|
||||
App.Comments.initialize();
|
||||
@@ -98,10 +105,15 @@ var initialize_modules = function() {
|
||||
App.WatchFormChanges.initialize();
|
||||
App.TreeNavigator.initialize();
|
||||
App.Documentable.initialize();
|
||||
App.Imageable.initialize();
|
||||
App.TagAutocomplete.initialize();
|
||||
App.PollsAdmin.initialize();
|
||||
App.Map.initialize();
|
||||
App.Polls.initialize();
|
||||
};
|
||||
|
||||
$(function(){
|
||||
Turbolinks.enableProgressBar()
|
||||
Turbolinks.enableProgressBar();
|
||||
|
||||
$(document).ready(initialize_modules);
|
||||
$(document).on('page:load', initialize_modules);
|
||||
|
||||
@@ -1,101 +1,156 @@
|
||||
App.Documentable =
|
||||
|
||||
initialize: ->
|
||||
@initializeDirectUploads()
|
||||
@initializeInterface()
|
||||
|
||||
initializeDirectUploads: ->
|
||||
inputFiles = $('.js-document-attachment')
|
||||
$.each inputFiles, (index, input) ->
|
||||
App.Documentable.initializeDirectUploadInput(input)
|
||||
|
||||
$('input.js-document-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.js-document-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')
|
||||
|
||||
166
app/assets/javascripts/imageable.js.coffee
Normal file
166
app/assets/javascripts/imageable.js.coffee
Normal 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')
|
||||
78
app/assets/javascripts/map.js.coffee
Normal file
78
app/assets/javascripts/map.js.coffee
Normal file
@@ -0,0 +1,78 @@
|
||||
App.Map =
|
||||
|
||||
initialize: ->
|
||||
maps = $('*[data-map]')
|
||||
|
||||
if maps.length > 0
|
||||
$.each maps, (index, map) ->
|
||||
App.Map.initializeMap map
|
||||
|
||||
initializeMap: (element) ->
|
||||
|
||||
mapCenterLatitude = $(element).data('map-center-latitude')
|
||||
mapCenterLongitude = $(element).data('map-center-longitude')
|
||||
markerLatitude = $(element).data('marker-latitude')
|
||||
markerLongitude = $(element).data('marker-longitude')
|
||||
zoom = $(element).data('map-zoom')
|
||||
mapTilesProvider = $(element).data('map-tiles-provider')
|
||||
mapAttribution = $(element).data('map-tiles-provider-attribution')
|
||||
latitudeInputSelector = $(element).data('latitude-input-selector')
|
||||
longitudeInputSelector = $(element).data('longitude-input-selector')
|
||||
zoomInputSelector = $(element).data('zoom-input-selector')
|
||||
removeMarkerSelector = $(element).data('marker-remove-selector')
|
||||
editable = $(element).data('marker-editable')
|
||||
marker = null;
|
||||
markerIcon = L.divIcon(
|
||||
className: 'map-marker'
|
||||
iconSize: [30, 30]
|
||||
iconAnchor: [15, 40]
|
||||
html: '<div class="map-icon"></div>')
|
||||
|
||||
createMarker = (latitude, longitude) ->
|
||||
markerLatLng = new (L.LatLng)(latitude, longitude)
|
||||
marker = L.marker(markerLatLng, { icon: markerIcon, draggable: editable })
|
||||
if editable
|
||||
marker.on 'dragend', updateFormfields
|
||||
marker.addTo(map)
|
||||
return marker
|
||||
|
||||
removeMarker = (e) ->
|
||||
e.preventDefault()
|
||||
if marker
|
||||
map.removeLayer(marker)
|
||||
marker = null;
|
||||
clearFormfields()
|
||||
return
|
||||
|
||||
moveOrPlaceMarker = (e) ->
|
||||
if marker
|
||||
marker.setLatLng(e.latlng)
|
||||
else
|
||||
marker = createMarker(e.latlng.lat, e.latlng.lng)
|
||||
|
||||
updateFormfields()
|
||||
return
|
||||
|
||||
updateFormfields = ->
|
||||
$(latitudeInputSelector).val marker.getLatLng().lat
|
||||
$(longitudeInputSelector).val marker.getLatLng().lng
|
||||
$(zoomInputSelector).val map.getZoom()
|
||||
return
|
||||
|
||||
clearFormfields = ->
|
||||
$(latitudeInputSelector).val ''
|
||||
$(longitudeInputSelector).val ''
|
||||
$(zoomInputSelector).val ''
|
||||
return
|
||||
|
||||
mapCenterLatLng = new (L.LatLng)(mapCenterLatitude, mapCenterLongitude)
|
||||
map = L.map(element.id).setView(mapCenterLatLng, zoom)
|
||||
L.tileLayer(mapTilesProvider, attribution: mapAttribution).addTo map
|
||||
|
||||
if markerLatitude && markerLongitude
|
||||
marker = createMarker(markerLatitude, markerLongitude)
|
||||
|
||||
if editable
|
||||
$(removeMarkerSelector).on 'click', removeMarker
|
||||
map.on 'zoomend', updateFormfields
|
||||
map.on 'click', moveOrPlaceMarker
|
||||
28
app/assets/javascripts/polls.js.coffee
Normal file
28
app/assets/javascripts/polls.js.coffee
Normal file
@@ -0,0 +1,28 @@
|
||||
App.Polls =
|
||||
generateToken: ->
|
||||
token = ''
|
||||
rand = ''
|
||||
for n in [0..5]
|
||||
rand = Math.random().toString(36).substr(2) # remove `0.`
|
||||
token = token + rand;
|
||||
|
||||
token = token.substring(0, 64)
|
||||
return token
|
||||
|
||||
replaceToken: ->
|
||||
for link in $('.js-question-answer')
|
||||
token_param = link.search.slice(-6)
|
||||
if token_param == "token="
|
||||
link.href = link.href + @token
|
||||
|
||||
initialize: ->
|
||||
@token = App.Polls.generateToken()
|
||||
App.Polls.replaceToken()
|
||||
|
||||
$(".js-question-answer").on
|
||||
click: =>
|
||||
token_message = $(".js-token-message")
|
||||
if !token_message.is(':visible')
|
||||
token_message.html(token_message.html() + "<br><strong>" + @token + "</strong>");
|
||||
token_message.show()
|
||||
false
|
||||
12
app/assets/javascripts/polls_admin.js.coffee
Normal file
12
app/assets/javascripts/polls_admin.js.coffee
Normal file
@@ -0,0 +1,12 @@
|
||||
App.PollsAdmin =
|
||||
|
||||
initialize: ->
|
||||
$("select[class='js-poll-shifts']").on
|
||||
change: ->
|
||||
switch ($(this).val())
|
||||
when 'vote_collection'
|
||||
$("select[class='js-shift-vote-collection-dates']").show();
|
||||
$("select[class='js-shift-recount-scrutiny-dates']").hide();
|
||||
when 'recount_scrutiny'
|
||||
$("select[class='js-shift-recount-scrutiny-dates']").show();
|
||||
$("select[class='js-shift-vote-collection-dates']").hide();
|
||||
34
app/assets/javascripts/tag_autocomplete.js.coffee
Normal file
34
app/assets/javascripts/tag_autocomplete.js.coffee
Normal 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();
|
||||
@@ -75,3 +75,5 @@ $accordion-content-color: foreground($accordion-background, $text);
|
||||
$tab-item-font-size: $base-font-size;
|
||||
$tab-item-padding: $line-height / 2 0;
|
||||
$tab-content-border: $border;
|
||||
|
||||
$orbit-bullet-diameter: 0.8rem;
|
||||
|
||||
@@ -8,38 +8,105 @@
|
||||
// 06. Polls
|
||||
// 07. Legislation
|
||||
// 08. CMS
|
||||
// 09. Map
|
||||
//
|
||||
|
||||
// 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 +121,8 @@ $admin-color: #cf3638;
|
||||
}
|
||||
}
|
||||
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
|
||||
&.text-center {
|
||||
@@ -81,6 +149,7 @@ $admin-color: #cf3638;
|
||||
}
|
||||
|
||||
table {
|
||||
|
||||
.break {
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -107,9 +176,19 @@ $admin-color: #cf3638;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.menu.simple .active {
|
||||
border-bottom: 2px solid $admin-color;
|
||||
color: $admin-color;
|
||||
.menu.simple {
|
||||
margin-bottom: $line-height / 2;
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
margin-bottom: $line-height / 3;
|
||||
}
|
||||
|
||||
.active {
|
||||
border-bottom: 2px solid $admin-color;
|
||||
color: $admin-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-panel {
|
||||
@@ -195,6 +274,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 +289,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 +300,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 +333,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,3 +978,51 @@ table {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 09. Map
|
||||
// --------------
|
||||
|
||||
.map {
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
|
||||
.map-marker {
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-top: -5px;
|
||||
|
||||
.map-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50% 50% 50% 0;
|
||||
background: #00cae9;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.map-icon::after {
|
||||
content: '';
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: 8px 0 0 8px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.map-attributtion {
|
||||
visibility: visible;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.map-marker {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.map-attributtion {
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@@ -16,4 +16,6 @@
|
||||
@import 'annotator_overrides';
|
||||
@import 'jquery-ui/datepicker';
|
||||
@import 'datepicker_overrides';
|
||||
@import 'documentable';
|
||||
@import 'jquery-ui/autocomplete';
|
||||
@import 'autocomplete_overrides';
|
||||
@import 'leaflet';
|
||||
|
||||
40
app/assets/stylesheets/autocomplete_overrides.scss
Normal file
40
app/assets/stylesheets/autocomplete_overrides.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +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;
|
||||
}
|
||||
|
||||
.js-document-attachment {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
@import 'consul_settings';
|
||||
@import 'custom_settings';
|
||||
@import 'foundation';
|
||||
@import 'motion-ui/motion-ui';
|
||||
|
||||
@include foundation-global-styles;
|
||||
@include foundation-grid;
|
||||
@@ -37,3 +38,7 @@
|
||||
@include foundation-title-bar;
|
||||
@include foundation-top-bar;
|
||||
@include foundation-menu-icon;
|
||||
@include foundation-orbit;
|
||||
|
||||
@include motion-ui-transitions;
|
||||
@include motion-ui-animations;
|
||||
|
||||
@@ -97,10 +97,6 @@
|
||||
content: '\72';
|
||||
}
|
||||
|
||||
.icon-documents::before {
|
||||
content: '\68';
|
||||
}
|
||||
|
||||
.icon-proposals::before {
|
||||
content: '\68';
|
||||
}
|
||||
@@ -260,3 +256,15 @@
|
||||
.icon-instagram::before {
|
||||
content: '\32';
|
||||
}
|
||||
|
||||
.icon-image::before {
|
||||
content: '\33';
|
||||
}
|
||||
|
||||
.icon-search-plus::before {
|
||||
content: '\34';
|
||||
}
|
||||
|
||||
.icon-search-minus::before {
|
||||
content: '\35';
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
// 16. Flags
|
||||
// 17. Activity
|
||||
// 18. Banners
|
||||
// 19. Documents
|
||||
// 19. Recommended Section Home
|
||||
// 20. Documents
|
||||
//
|
||||
|
||||
// 01. Global styles
|
||||
@@ -205,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +303,10 @@ a {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.back:not([class^="icon-"]) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.tabs-content {
|
||||
border: 0;
|
||||
}
|
||||
@@ -320,10 +346,28 @@ a {
|
||||
background: $brand;
|
||||
}
|
||||
|
||||
.truncate-horizontal-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.align-top {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.aling-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
// 02. Header
|
||||
// ----------
|
||||
|
||||
@@ -367,7 +411,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;
|
||||
@@ -587,7 +635,7 @@ header {
|
||||
text-align: left;
|
||||
|
||||
@include breakpoint(medium) {
|
||||
margin-right: $line-height * 1.5;
|
||||
margin-right: $line-height;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -2136,68 +2184,146 @@ table {
|
||||
}
|
||||
}
|
||||
|
||||
// 19. Documents
|
||||
.document-form form {
|
||||
// 19. Recommended Section Home
|
||||
// ----------------------------
|
||||
|
||||
.radio-buttons {
|
||||
label {
|
||||
margin-right: $line-height;
|
||||
}
|
||||
.home-page {
|
||||
|
||||
.push {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.section-recommended {
|
||||
padding: $line-height * 2 0;
|
||||
|
||||
h2 {
|
||||
margin-bottom: $line-height * 2;
|
||||
}
|
||||
|
||||
.source-option-link {
|
||||
input {
|
||||
padding-bottom: 0;
|
||||
.debates,
|
||||
.proposals,
|
||||
.budget-investments {
|
||||
|
||||
@include breakpoint(medium) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
@include breakpoint(small) {
|
||||
margin-bottom: $line-height;
|
||||
}
|
||||
|
||||
label {
|
||||
&.error {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.button.hollow {
|
||||
margin-top: rem-calc(15);
|
||||
}
|
||||
}
|
||||
|
||||
.source-option-file {
|
||||
.file-name {
|
||||
label {
|
||||
.card {
|
||||
|
||||
@include breakpoint(small medium) {
|
||||
float: none;
|
||||
}
|
||||
|
||||
@include breakpoint(large) {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
.card-section {
|
||||
padding: $line-height 0;
|
||||
max-width: rem-calc(300);
|
||||
margin: 0 auto;
|
||||
|
||||
p {
|
||||
font-size: rem-calc(15);
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(small medium) {
|
||||
float: none;
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.orbit {
|
||||
height: rem-calc(300);
|
||||
|
||||
@include breakpoint(large) {
|
||||
float: left;
|
||||
margin-bottom: 0;
|
||||
margin-top: $line-height / 2;
|
||||
margin-left: $line-height;
|
||||
}
|
||||
.orbit-wrapper {
|
||||
max-height: rem-calc(250);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.orbit-bullets {
|
||||
@include orbit-bullets;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-errors {
|
||||
margin-bottom: $line-height;
|
||||
.card .orbit .orbit-wrapper .truncate {
|
||||
background: image-url('truncate.png');
|
||||
background-repeat: repeat-x;
|
||||
bottom: 0;
|
||||
height: rem-calc(20);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.debates-inner {
|
||||
border-top: 4px solid $debates;
|
||||
}
|
||||
|
||||
.proposals-inner {
|
||||
border-top: 4px solid $proposals;
|
||||
}
|
||||
|
||||
.budget-investments-inner {
|
||||
border-top: 4px solid $budget;
|
||||
}
|
||||
|
||||
.debates-inner,
|
||||
.proposals-inner,
|
||||
.budget-investments-inner {
|
||||
background: #fff;
|
||||
max-height: rem-calc(350);
|
||||
|
||||
@include breakpoint(small) {
|
||||
max-height: rem-calc(400);
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: $line-height;
|
||||
margin-bottom: 0;
|
||||
font-size: rem-calc(18);
|
||||
min-height: rem-calc(50);
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: $small-font-size;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.carousel-image {
|
||||
|
||||
.card .orbit {
|
||||
height: rem-calc(480);
|
||||
|
||||
.orbit-wrapper {
|
||||
max-height: rem-calc(450);
|
||||
}
|
||||
}
|
||||
|
||||
.debates-inner,
|
||||
.proposals-inner,
|
||||
.budget-investments-inner {
|
||||
max-height: rem-calc(500);
|
||||
|
||||
@include breakpoint(small) {
|
||||
max-height: rem-calc(600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.carousel-image .orbit-wrapper img {
|
||||
display: block;
|
||||
|
||||
@include breakpoint(small) {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 20. Documents
|
||||
// -------------
|
||||
|
||||
.documents-list {
|
||||
|
||||
table {
|
||||
@@ -2254,6 +2380,5 @@ table {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,11 +456,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,6 +474,7 @@ $border-dark: darken($border, 10%);
|
||||
}
|
||||
|
||||
.draft-allegation {
|
||||
|
||||
@include breakpoint(medium) {
|
||||
display: flex;
|
||||
padding-left: 0.9375rem;
|
||||
@@ -490,7 +491,6 @@ $border-dark: darken($border, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
// Panel calcs for desktop
|
||||
@media screen and (min-width: 40em) {
|
||||
.calc-index {
|
||||
width: calc(35% - 25px);
|
||||
@@ -506,6 +506,7 @@ $border-dark: darken($border, 10%);
|
||||
width: rem-calc(50);
|
||||
|
||||
.draft-panel {
|
||||
|
||||
.panel-title {
|
||||
display: none;
|
||||
}
|
||||
@@ -909,19 +910,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;
|
||||
@@ -933,35 +930,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -969,6 +967,7 @@ $border-dark: darken($border, 10%);
|
||||
|
||||
// 09. Legislation comments
|
||||
// -----------------
|
||||
|
||||
.legislation-comments {
|
||||
|
||||
.pull-right {
|
||||
@@ -1017,6 +1016,7 @@ $border-dark: darken($border, 10%);
|
||||
|
||||
// 10. Legislation draft comment
|
||||
// -----------------
|
||||
|
||||
.legislation-comment {
|
||||
|
||||
.annotation-share-comment {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Table of Contents
|
||||
//
|
||||
// 01. Logo
|
||||
//
|
||||
// 02. Orbit bullets
|
||||
// 03. Direct uploads
|
||||
// ------------------
|
||||
|
||||
// 01. Logo
|
||||
// --------
|
||||
@@ -30,3 +32,108 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 02. Orbit bullet
|
||||
// ----------------
|
||||
|
||||
@mixin orbit-bullets {
|
||||
@include disable-mouse-outline;
|
||||
position: relative;
|
||||
margin-top: $orbit-bullet-margin-top;
|
||||
margin-bottom: $orbit-bullet-margin-bottom;
|
||||
text-align: center;
|
||||
|
||||
button {
|
||||
width: $orbit-bullet-diameter;
|
||||
height: $orbit-bullet-diameter;
|
||||
margin: $orbit-bullet-margin;
|
||||
|
||||
border-radius: 50%;
|
||||
background-color: $orbit-bullet-background;
|
||||
|
||||
&:hover {
|
||||
background-color: $orbit-bullet-background-active;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: $orbit-bullet-background-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 03. Direct uploads
|
||||
// ------------------
|
||||
|
||||
@mixin direct-uploads {
|
||||
|
||||
.cached-image {
|
||||
max-width: rem-calc(150);
|
||||
max-height: rem-calc(150);
|
||||
}
|
||||
|
||||
.progress-bar-placeholder {
|
||||
display: none;
|
||||
margin-bottom: $line-height;
|
||||
}
|
||||
|
||||
.document,
|
||||
.image {
|
||||
|
||||
.document-attachment,
|
||||
.image-attachment {
|
||||
padding-left: 0;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-errors {
|
||||
|
||||
> .js-image-attachment,
|
||||
> .js-document-attachment {
|
||||
display: none;
|
||||
|
||||
~ .error {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,14 +249,13 @@
|
||||
.proposal-form,
|
||||
.budget-investment-form,
|
||||
.spending-proposal-form,
|
||||
.document-form,
|
||||
.topic-new,
|
||||
.topic-form {
|
||||
|
||||
.icon-debates,
|
||||
.icon-proposals,
|
||||
.icon-budget,
|
||||
.icon-documents {
|
||||
.icon-image {
|
||||
font-size: rem-calc(50);
|
||||
line-height: $line-height;
|
||||
opacity: 0.5;
|
||||
@@ -267,7 +266,7 @@
|
||||
}
|
||||
|
||||
.icon-proposals,
|
||||
.icon-documents {
|
||||
.icon-image {
|
||||
color: $proposals;
|
||||
}
|
||||
|
||||
@@ -301,14 +300,21 @@
|
||||
|
||||
.proposal-form,
|
||||
.topic-form,
|
||||
.topic-new,
|
||||
.document-form {
|
||||
.topic-new {
|
||||
|
||||
.recommendations li::before {
|
||||
color: $proposals;
|
||||
}
|
||||
}
|
||||
|
||||
.budget-investment-new,
|
||||
.proposal-form,
|
||||
.proposal-edit,
|
||||
.polls-form,
|
||||
.poll-question-form {
|
||||
@include direct-uploads;
|
||||
}
|
||||
|
||||
// 03. Show participation
|
||||
// ----------------------
|
||||
|
||||
@@ -329,8 +335,16 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.callout.proposal-retired {
|
||||
font-size: $base-font-size;
|
||||
.callout {
|
||||
&.token-message {
|
||||
background-color: #fff;
|
||||
border-color: $info-border;
|
||||
color: $color-info;
|
||||
}
|
||||
|
||||
&.proposal-retired {
|
||||
font-size: $base-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
.social-share-full .social-share-button {
|
||||
@@ -349,8 +363,7 @@
|
||||
width: rem-calc(48);
|
||||
}
|
||||
|
||||
.edit-debate,
|
||||
.edit-proposal {
|
||||
.edit-debate {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -640,6 +653,71 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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: rem-calc(300);
|
||||
margin: 0 auto;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
padding-top: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(medium) {
|
||||
|
||||
.panel {
|
||||
|
||||
&.with-image {
|
||||
padding: 0 $line-height / 2 0 0;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
height: 100%;
|
||||
min-height: rem-calc(245);
|
||||
width: rem-calc(140);
|
||||
}
|
||||
}
|
||||
|
||||
.column:first-child {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.column:nth-child(2) {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.column:last-child:not(:first-child) {
|
||||
padding-top: $line-height / 2;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 12rem;
|
||||
}
|
||||
|
||||
.budget-investment-content {
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.debate,
|
||||
.proposal,
|
||||
.investment-project,
|
||||
@@ -755,7 +833,7 @@
|
||||
background: image-url('truncate.png');
|
||||
background-repeat: repeat-x;
|
||||
bottom: 0;
|
||||
height: 24px;
|
||||
height: rem-calc(24);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -769,12 +847,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.document-form {
|
||||
max-width: 75rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.more-info {
|
||||
clear: both;
|
||||
color: $text-medium;
|
||||
@@ -859,6 +931,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;
|
||||
@@ -1468,18 +1553,8 @@
|
||||
// 08. Polls
|
||||
// ----------------------
|
||||
|
||||
.dark-heading {
|
||||
background: #2d3e50;
|
||||
color: #fff;
|
||||
|
||||
.title {
|
||||
color: #92ba48;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #fff;
|
||||
color: $brand;
|
||||
}
|
||||
.polls-show-header {
|
||||
background: #fafafa;
|
||||
|
||||
.callout {
|
||||
|
||||
@@ -1495,28 +1570,117 @@
|
||||
color: $color-alert;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
background: #314253;
|
||||
padding: $line-height;
|
||||
.poll-more-info,
|
||||
.poll-more-info-answers {
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
@include breakpoint(medium) {
|
||||
border-top: rem-calc(6) solid #92ba48;
|
||||
.poll-more-info-answers {
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.column:nth-child(odd) {
|
||||
border-right: 2px solid $text;
|
||||
}
|
||||
|
||||
.answer-divider {
|
||||
border-bottom: 2px solid $text;
|
||||
border-right: 0 !important;
|
||||
margin-bottom: $line-height;
|
||||
padding-bottom: $line-height;
|
||||
}
|
||||
|
||||
.answer-description {
|
||||
height: 100%;
|
||||
|
||||
&.short {
|
||||
height: $line-height * 12;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a:not(.button) {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
.orbit-bullets button {
|
||||
background-color: #ccc;
|
||||
height: $line-height / 2;
|
||||
width: $line-height / 2;
|
||||
|
||||
&.is-active {
|
||||
background-color: $brand;
|
||||
}
|
||||
}
|
||||
|
||||
.back,
|
||||
.icon-angle-left {
|
||||
color: #fff;
|
||||
.orbit-container {
|
||||
height: 100% !important;
|
||||
max-height: none !important;
|
||||
|
||||
li {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.polls-show-header {
|
||||
min-height: $line-height * 8;
|
||||
.orbit-slide {
|
||||
max-height: none !important;
|
||||
|
||||
img {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
}
|
||||
|
||||
.orbit-caption {
|
||||
background: #eee;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.orbit-next,
|
||||
.orbit-previous {
|
||||
background: rgba(34, 34, 34, 0.25);
|
||||
}
|
||||
|
||||
.zoom-link {
|
||||
background: #fff;
|
||||
border-radius: rem-calc(48);
|
||||
color: #000;
|
||||
font-size: rem-calc(24);
|
||||
font-weight: bold;
|
||||
height: rem-calc(48);
|
||||
line-height: rem-calc(48);
|
||||
right: 12px;
|
||||
padding-top: rem-calc(4);
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 24px;
|
||||
width: rem-calc(48);
|
||||
z-index: 9;
|
||||
|
||||
&:hover {
|
||||
background: $dark;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.image-container {
|
||||
background: #fafafa;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.poll {
|
||||
|
||||
&.with-image {
|
||||
|
||||
@include breakpoint(medium) {
|
||||
padding: 0 $line-height / 2 0 0;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1614,9 +1778,13 @@
|
||||
}
|
||||
|
||||
.section-title-divider {
|
||||
border-bottom: 2px solid $brand;
|
||||
color: $brand;
|
||||
margin-bottom: $line-height;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #000;
|
||||
margin: $line-height 0;
|
||||
|
||||
span {
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-question {
|
||||
@@ -1637,6 +1805,10 @@
|
||||
margin-right: $line-height / 4;
|
||||
min-width: rem-calc(168);
|
||||
|
||||
@include breakpoint(medium down) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.answered {
|
||||
background: #f4f8ec;
|
||||
border: 2px solid #92ba48;
|
||||
|
||||
@@ -15,7 +15,7 @@ class Admin::Poll::BoothAssignmentsController < Admin::Poll::BaseController
|
||||
end
|
||||
|
||||
def show
|
||||
@booth_assignment = @poll.booth_assignments.includes(:total_results, :voters,
|
||||
@booth_assignment = @poll.booth_assignments.includes(:recounts, :voters,
|
||||
officer_assignments: [officer: [:user]]).find(params[:id])
|
||||
@voters_by_date = @booth_assignment.voters.group_by {|v| v.created_at.to_date}
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@ class Admin::Poll::OfficerAssignmentsController < Admin::Poll::BaseController
|
||||
@officer = ::Poll::Officer.includes(:user).find(officer_assignment_params[:officer_id])
|
||||
@officer_assignments = ::Poll::OfficerAssignment.
|
||||
joins(:booth_assignment).
|
||||
includes(:total_results, booth_assignment: :booth).
|
||||
includes(:recounts, booth_assignment: :booth).
|
||||
where("officer_id = ? AND poll_booth_assignments.poll_id = ?", @officer.id, @poll.id).
|
||||
order(:date)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Admin::Poll::PollsController < Admin::Poll::BaseController
|
||||
load_and_authorize_resource
|
||||
|
||||
before_action :load_search, only: [:search_booths, :search_questions, :search_officers]
|
||||
before_action :load_search, only: [:search_booths, :search_officers]
|
||||
before_action :load_geozones, only: [:new, :create, :edit, :update]
|
||||
|
||||
def index
|
||||
@@ -47,25 +47,6 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController
|
||||
redirect_to admin_poll_path(@poll), notice: notice
|
||||
end
|
||||
|
||||
def remove_question
|
||||
question = ::Poll::Question.find(params[:question_id])
|
||||
|
||||
if @poll.questions.include? question
|
||||
@poll.questions.delete(question)
|
||||
notice = t("admin.polls.flash.question_removed")
|
||||
else
|
||||
notice = t("admin.polls.flash.error_on_question_removed")
|
||||
end
|
||||
redirect_to admin_poll_path(@poll), notice: notice
|
||||
end
|
||||
|
||||
def search_questions
|
||||
@questions = ::Poll::Question.where("poll_id IS ? OR poll_id != ?", nil, @poll.id).search(search: @search).order(title: :asc)
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_geozones
|
||||
@@ -73,7 +54,9 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController
|
||||
end
|
||||
|
||||
def poll_params
|
||||
params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, geozone_ids: [])
|
||||
params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, :summary, :description,
|
||||
geozone_ids: [],
|
||||
image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy])
|
||||
end
|
||||
|
||||
def search_params
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
class Admin::Poll::Questions::Answers::ImagesController < Admin::Poll::BaseController
|
||||
before_action :load_answer
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def new
|
||||
@answer = ::Poll::Question::Answer.find(params[:answer_id])
|
||||
end
|
||||
|
||||
def create
|
||||
@answer = ::Poll::Question::Answer.find(params[:answer_id])
|
||||
@answer.attributes = images_params
|
||||
|
||||
if @answer.save
|
||||
redirect_to admin_answer_images_path(@answer),
|
||||
notice: "Image uploaded successfully"
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def images_params
|
||||
params.require(:poll_question_answer).permit(:answer_id,
|
||||
images_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy])
|
||||
end
|
||||
|
||||
def load_answer
|
||||
@answer = ::Poll::Question::Answer.find(params[:answer_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
class Admin::Poll::Questions::Answers::VideosController < Admin::Poll::BaseController
|
||||
before_action :load_answer, only: [:index, :new, :create]
|
||||
before_action :load_video, only: [:edit, :update, :destroy]
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def new
|
||||
@video = ::Poll::Question::Answer::Video.new
|
||||
end
|
||||
|
||||
def create
|
||||
@video = ::Poll::Question::Answer::Video.new(video_params)
|
||||
|
||||
if @video.save
|
||||
redirect_to admin_answer_videos_path(@answer),
|
||||
notice: t("flash.actions.create.poll_question_answer_video")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @video.update(video_params)
|
||||
redirect_to admin_answer_videos_path(@video.answer_id),
|
||||
notice: t("flash.actions.save_changes.notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @video.destroy
|
||||
notice = t("flash.actions.destroy.poll_question_answer_video")
|
||||
else
|
||||
notice = t("flash.actions.destroy.error")
|
||||
end
|
||||
redirect_to :back, notice: notice
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def video_params
|
||||
params.require(:poll_question_answer_video).permit(:title, :url, :answer_id)
|
||||
end
|
||||
|
||||
def load_answer
|
||||
@answer = ::Poll::Question::Answer.find(params[:answer_id])
|
||||
end
|
||||
|
||||
def load_video
|
||||
@video = ::Poll::Question::Answer::Video.find(params[:id])
|
||||
end
|
||||
end
|
||||
52
app/controllers/admin/poll/questions/answers_controller.rb
Normal file
52
app/controllers/admin/poll/questions/answers_controller.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
class Admin::Poll::Questions::AnswersController < Admin::Poll::BaseController
|
||||
before_action :load_answer, only: [:show, :edit, :update, :documents]
|
||||
|
||||
load_and_authorize_resource :question, class: "::Poll::Question"
|
||||
|
||||
def new
|
||||
@answer = ::Poll::Question::Answer.new
|
||||
end
|
||||
|
||||
def create
|
||||
@answer = ::Poll::Question::Answer.new(answer_params)
|
||||
|
||||
if @answer.save
|
||||
redirect_to admin_question_path(@answer.question),
|
||||
notice: t("flash.actions.create.poll_question_answer")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @answer.update(answer_params)
|
||||
redirect_to admin_question_path(@answer.question),
|
||||
notice: t("flash.actions.save_changes.notice")
|
||||
else
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
||||
|
||||
def documents
|
||||
@documents = @answer.documents
|
||||
|
||||
render 'admin/poll/questions/answers/documents'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def answer_params
|
||||
params.require(:poll_question_answer).permit(:title, :description, :question_id, documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy])
|
||||
end
|
||||
|
||||
def load_answer
|
||||
@answer = ::Poll::Question::Answer.find(params[:id] || params[:answer_id])
|
||||
end
|
||||
|
||||
end
|
||||
@@ -22,7 +22,6 @@ class Admin::Poll::QuestionsController < Admin::Poll::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::Poll::BaseController
|
||||
end
|
||||
|
||||
def show
|
||||
@document = Document.new(documentable: @question)
|
||||
end
|
||||
|
||||
def edit
|
||||
@@ -58,8 +56,7 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController
|
||||
private
|
||||
|
||||
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])
|
||||
params.require(:poll_question).permit(:poll_id, :title, :question, :proposal_id, :valid_answers, :video_url)
|
||||
end
|
||||
|
||||
def search_params
|
||||
|
||||
@@ -3,7 +3,7 @@ class Admin::Poll::RecountsController < Admin::Poll::BaseController
|
||||
|
||||
def index
|
||||
@booth_assignments = @poll.booth_assignments.
|
||||
includes(:booth, :total_results, :voters).
|
||||
includes(:booth, :recounts, :voters).
|
||||
order("poll_booths.name").
|
||||
page(params[:page]).per(50)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
class Admin::Poll::ShiftsController < Admin::Poll::BaseController
|
||||
|
||||
before_action :load_booth
|
||||
before_action :load_polls
|
||||
before_action :load_officer
|
||||
|
||||
def new
|
||||
@@ -14,10 +13,10 @@ class Admin::Poll::ShiftsController < Admin::Poll::BaseController
|
||||
@officer = @shift.officer
|
||||
|
||||
if @shift.save
|
||||
notice = t("admin.poll_shifts.flash.create")
|
||||
redirect_to new_admin_booth_shift_path(@shift.booth), notice: notice
|
||||
redirect_to new_admin_booth_shift_path(@shift.booth), notice: t("admin.poll_shifts.flash.create")
|
||||
else
|
||||
load_shifts
|
||||
flash[:error] = t("admin.poll_shifts.flash.date_missing")
|
||||
render :new
|
||||
end
|
||||
end
|
||||
@@ -30,7 +29,7 @@ class Admin::Poll::ShiftsController < Admin::Poll::BaseController
|
||||
end
|
||||
|
||||
def search_officers
|
||||
@officers = User.search(params[:search]).order(username: :asc)
|
||||
@officers = User.search(params[:search]).order(username: :asc).select { |o| o.poll_officer? == true }
|
||||
end
|
||||
|
||||
private
|
||||
@@ -39,10 +38,6 @@ class Admin::Poll::ShiftsController < Admin::Poll::BaseController
|
||||
@booth = ::Poll::Booth.find(params[:booth_id])
|
||||
end
|
||||
|
||||
def load_polls
|
||||
@polls = ::Poll.current_or_incoming
|
||||
end
|
||||
|
||||
def load_shifts
|
||||
@shifts = @booth.shifts
|
||||
end
|
||||
@@ -54,7 +49,7 @@ class Admin::Poll::ShiftsController < Admin::Poll::BaseController
|
||||
end
|
||||
|
||||
def shift_params
|
||||
params.require(:shift).permit(:booth_id, :officer_id, :date)
|
||||
shift_params = params.require(:shift).permit(:booth_id, :officer_id, :task, date:[:vote_collection_date, :recount_scrutiny_date])
|
||||
shift_params.merge(date: shift_params[:date]["#{shift_params[:task]}_date".to_sym])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -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']
|
||||
@@ -14,6 +14,13 @@ class Admin::SettingsController < Admin::BaseController
|
||||
redirect_to admin_settings_path, notice: t("admin.settings.flash.updated")
|
||||
end
|
||||
|
||||
def update_map
|
||||
Setting["map_latitude"] = params[:latitude].to_f
|
||||
Setting["map_longitude"] = params[:longitude].to_f
|
||||
Setting["map_zoom"] = params[:zoom].to_i
|
||||
redirect_to admin_settings_path, notice: t("admin.settings.index.map.flash.update")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def settings_params
|
||||
|
||||
@@ -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,12 @@ 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],
|
||||
map_location_attributes: [:latitude, :longitude, :zoom])
|
||||
end
|
||||
|
||||
def load_ballot
|
||||
|
||||
@@ -4,17 +4,22 @@ module CommentableActions
|
||||
include Search
|
||||
|
||||
def index
|
||||
@resources = @search_terms.present? ? resource_model.search(@search_terms) : resource_model.all
|
||||
@resources = @advanced_search_terms.present? ? @resources.filter(@advanced_search_terms) : @resources
|
||||
@resources = resource_model.all
|
||||
|
||||
@resources = @current_order == "recommendations" && current_user.present? ? @resources.recommendations(current_user) : @resources.for_render
|
||||
@resources = @resources.search(@search_terms) if @search_terms.present?
|
||||
@resources = @advanced_search_terms.present? ? @resources.filter(@advanced_search_terms) : @resources
|
||||
@resources = @resources.tagged_with(@tag_filter) if @tag_filter
|
||||
@resources = @resources.page(params[:page]).for_render.send("sort_by_#{@current_order}")
|
||||
|
||||
@resources = @resources.page(params[:page]).send("sort_by_#{@current_order}")
|
||||
|
||||
index_customization if index_customization.present?
|
||||
|
||||
@tag_cloud = tag_cloud
|
||||
@banners = Banner.with_active
|
||||
|
||||
set_resource_votes(@resources)
|
||||
|
||||
set_resources_instance
|
||||
end
|
||||
|
||||
@@ -57,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
|
||||
@@ -112,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
|
||||
|
||||
@@ -10,7 +10,7 @@ class DebatesController < ApplicationController
|
||||
|
||||
invisible_captcha only: [:create, :update], honeypot: :subtitle
|
||||
|
||||
has_orders %w{hot_score confidence_score created_at relevance}, only: :index
|
||||
has_orders ->(c) { Debate.debates_orders(c.current_user) }, only: :index
|
||||
has_orders %w{most_voted newest oldest}, only: :show
|
||||
|
||||
load_and_authorize_resource
|
||||
|
||||
49
app/controllers/direct_uploads_controller.rb
Normal file
49
app/controllers/direct_uploads_controller.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
26
app/controllers/images_controller.rb
Normal file
26
app/controllers/images_controller.rb
Normal 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
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,7 @@ class NotificationsController < ApplicationController
|
||||
|
||||
def show
|
||||
@notification = current_user.notifications.find(params[:id])
|
||||
redirect_to url_for(@notification.linkable_resource)
|
||||
redirect_to linkable_resource_path(@notification)
|
||||
end
|
||||
|
||||
def mark_all_as_read
|
||||
@@ -25,4 +25,13 @@ class NotificationsController < ApplicationController
|
||||
@notification.mark_as_read
|
||||
end
|
||||
|
||||
def linkable_resource_path(notification)
|
||||
case notification.linkable_resource.class.name
|
||||
when "Budget::Investment"
|
||||
budget_investment_path @notification.linkable_resource.budget, @notification.linkable_resource
|
||||
else
|
||||
url_for @notification.linkable_resource
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -7,7 +7,9 @@ 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 do |poll|
|
||||
poll.ends_at > 2.weeks.ago && poll.expired? || poll.ends_at.today?
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -26,9 +26,7 @@ class Officing::ResultsController < Officing::BaseController
|
||||
@partial_results = ::Poll::PartialResult.includes(:question).
|
||||
where(booth_assignment_id: index_params[:booth_assignment_id]).
|
||||
where(date: index_params[:date])
|
||||
@whites = ::Poll::WhiteResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
|
||||
@nulls = ::Poll::NullResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
|
||||
@total = ::Poll::TotalResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
|
||||
@recounts = ::Poll::Recount.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -52,62 +50,37 @@ 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 if count.blank?
|
||||
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
|
||||
|
||||
build_white_results
|
||||
build_null_results
|
||||
build_total_results
|
||||
build_recounts
|
||||
end
|
||||
|
||||
def build_white_results
|
||||
if results_params[:whites].present?
|
||||
white_result = ::Poll::WhiteResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
|
||||
date: results_params[:date])
|
||||
white_result.officer_assignment_id = @officer_assignment.id
|
||||
white_result.amount = results_params[:whites].to_i
|
||||
white_result.author = current_user
|
||||
white_result.origin = 'booth'
|
||||
@results << white_result
|
||||
end
|
||||
end
|
||||
|
||||
def build_null_results
|
||||
if results_params[:nulls].present?
|
||||
null_result = ::Poll::NullResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
|
||||
date: results_params[:date])
|
||||
null_result.officer_assignment_id = @officer_assignment.id
|
||||
null_result.amount = results_params[:nulls].to_i
|
||||
null_result.author = current_user
|
||||
null_result.origin = 'booth'
|
||||
@results << null_result
|
||||
end
|
||||
end
|
||||
|
||||
def build_total_results
|
||||
if results_params[:total].present?
|
||||
total_result = ::Poll::TotalResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
|
||||
date: results_params[:date])
|
||||
total_result.officer_assignment_id = @officer_assignment.id
|
||||
total_result.amount = results_params[:total].to_i
|
||||
total_result.author = current_user
|
||||
total_result.origin = 'booth'
|
||||
@results << total_result
|
||||
def build_recounts
|
||||
recount = ::Poll::Recount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
|
||||
date: results_params[:date])
|
||||
recount.officer_assignment_id = @officer_assignment.id
|
||||
recount.author = current_user
|
||||
recount.origin = 'booth'
|
||||
[:whites, :nulls, :total].each do |recount_type|
|
||||
if results_params[recount_type].present?
|
||||
recount["#{recount_type.to_s.singularize}_amount"] = results_params[recount_type].to_i
|
||||
end
|
||||
end
|
||||
@results << recount
|
||||
end
|
||||
|
||||
def go_back_to_new(alert = nil)
|
||||
@@ -120,7 +93,7 @@ class Officing::ResultsController < Officing::BaseController
|
||||
end
|
||||
|
||||
def load_poll
|
||||
@poll = ::Poll.expired.includes(:questions).find(params[:poll_id])
|
||||
@poll = ::Poll.includes(:questions).find(params[:poll_id])
|
||||
end
|
||||
|
||||
def load_officer_assignment
|
||||
|
||||
@@ -3,7 +3,8 @@ class Officing::VotersController < Officing::BaseController
|
||||
|
||||
def new
|
||||
@user = User.find(params[:id])
|
||||
@polls = Poll.answerable_by(@user)
|
||||
booths = current_user.poll_officer.shifts.current.vote_collection.pluck(:booth_id).uniq
|
||||
@polls = Poll.answerable_by(@user).where(id: Poll::BoothAssignment.where(booth: booths).pluck(:poll_id).uniq)
|
||||
end
|
||||
|
||||
def create
|
||||
@@ -12,7 +13,9 @@ class Officing::VotersController < Officing::BaseController
|
||||
@voter = Poll::Voter.new(document_type: @user.document_type,
|
||||
document_number: @user.document_number,
|
||||
user: @user,
|
||||
poll: @poll)
|
||||
poll: @poll,
|
||||
origin: "booth",
|
||||
officer: current_user.poll_officer)
|
||||
@voter.save!
|
||||
end
|
||||
|
||||
|
||||
@@ -5,25 +5,16 @@ class Polls::QuestionsController < ApplicationController
|
||||
|
||||
has_orders %w{most_voted newest oldest}, only: :show
|
||||
|
||||
def show
|
||||
@commentable = @question.proposal.present? ? @question.proposal : @question
|
||||
@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
|
||||
|
||||
def answer
|
||||
answer = @question.answers.find_or_initialize_by(author: current_user)
|
||||
token = params[:token]
|
||||
|
||||
answer.answer = params[:answer]
|
||||
answer.touch if answer.persisted?
|
||||
answer.save!
|
||||
answer.record_voter_participation
|
||||
answer.record_voter_participation(token)
|
||||
|
||||
@answers_by_question_id = {@question.id => params[:answer]}
|
||||
@answers_by_question_id = { @question.id => params[:answer] }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
class PollsController < ApplicationController
|
||||
|
||||
include PollsHelper
|
||||
|
||||
load_and_authorize_resource
|
||||
|
||||
has_filters %w{current expired incoming}
|
||||
@@ -12,7 +14,7 @@ class PollsController < ApplicationController
|
||||
|
||||
def show
|
||||
@questions = @poll.questions.for_render.sort_for_list
|
||||
|
||||
@token = poll_voter_token(@poll, current_user)
|
||||
@answers_by_question_id = {}
|
||||
poll_answers = ::Poll::Answer.by_question(@poll.question_ids).by_author(current_user.try(:id))
|
||||
poll_answers.each do |answer|
|
||||
|
||||
@@ -9,7 +9,7 @@ class ProposalsController < ApplicationController
|
||||
|
||||
invisible_captcha only: [:create, :update], honeypot: :subtitle
|
||||
|
||||
has_orders %w{hot_score confidence_score created_at relevance archival_date}, only: :index
|
||||
has_orders ->(c) { Proposal.proposals_orders(c.current_user) }, only: :index
|
||||
has_orders %w{most_voted newest oldest}, only: :show
|
||||
|
||||
load_and_authorize_resource
|
||||
@@ -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,9 @@ 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],
|
||||
map_location_attributes: [:latitude, :longitude, :zoom])
|
||||
end
|
||||
|
||||
def retired_params
|
||||
@@ -113,7 +113,7 @@ class ProposalsController < ApplicationController
|
||||
end
|
||||
|
||||
def load_featured
|
||||
return unless !@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?
|
||||
return unless !@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank? && @current_order != "recommendations"
|
||||
@featured_proposals = Proposal.not_archived.sort_by_confidence_score.limit(3)
|
||||
if @featured_proposals.present?
|
||||
set_featured_proposal_votes(@featured_proposals)
|
||||
|
||||
11
app/controllers/tags_controller.rb
Normal file
11
app/controllers/tags_controller.rb
Normal 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
|
||||
@@ -1,12 +1,10 @@
|
||||
class WelcomeController < ApplicationController
|
||||
skip_authorization_check
|
||||
before_action :set_user_recommendations, only: :index, if: :current_user
|
||||
|
||||
layout "devise", only: [:welcome, :verification]
|
||||
|
||||
def index
|
||||
if current_user
|
||||
redirect_to :proposals
|
||||
end
|
||||
end
|
||||
|
||||
def welcome
|
||||
@@ -16,4 +14,11 @@ class WelcomeController < ApplicationController
|
||||
redirect_to verification_path if signed_in?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user_recommendations
|
||||
@recommended_debates = Debate.recommendations(current_user).sort_by_recommendations.limit(3)
|
||||
@recommended_proposals = Proposal.recommendations(current_user).sort_by_recommendations.limit(3)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -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 questions answers].include? controller_name
|
||||
end
|
||||
|
||||
def menu_profiles?
|
||||
|
||||
@@ -4,4 +4,12 @@ module DebatesHelper
|
||||
Debate.all.featured.count > 0
|
||||
end
|
||||
|
||||
end
|
||||
def empty_recommended_debates_message_text(user)
|
||||
if user.interests.any?
|
||||
t('debates.index.recommendations.without_results')
|
||||
else
|
||||
t('debates.index.recommendations.without_interests')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
16
app/helpers/direct_uploads_helper.rb
Normal file
16
app/helpers/direct_uploads_helper.rb
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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: 'js-document-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
|
||||
|
||||
40
app/helpers/imageables_helper.rb
Normal file
40
app/helpers/imageables_helper.rb
Normal 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
|
||||
79
app/helpers/images_helper.rb
Normal file
79
app/helpers/images_helper.rb
Normal 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
|
||||
67
app/helpers/map_locations_helper.rb
Normal file
67
app/helpers/map_locations_helper.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
module MapLocationsHelper
|
||||
|
||||
def map_location_available?(map_location)
|
||||
map_location.present? && map_location.available?
|
||||
end
|
||||
|
||||
def map_location_latitude(map_location)
|
||||
map_location.present? && map_location.latitude.present? ? map_location.latitude : Setting["map_latitude"]
|
||||
end
|
||||
|
||||
def map_location_longitude(map_location)
|
||||
map_location.present? && map_location.longitude.present? ? map_location.longitude : Setting["map_longitude"]
|
||||
end
|
||||
|
||||
def map_location_zoom(map_location)
|
||||
map_location.present? && map_location.zoom.present? ? map_location.zoom : Setting["map_zoom"]
|
||||
end
|
||||
|
||||
def map_location_input_id(prefix, attribute)
|
||||
"#{prefix}_map_location_attributes_#{attribute}"
|
||||
end
|
||||
|
||||
def map_location_remove_marker_link_id(map_location)
|
||||
"remove-marker-link-#{dom_id(map_location)}"
|
||||
end
|
||||
|
||||
def render_map(map_location, parent_class, editable, remove_marker_label)
|
||||
map = content_tag_for :div,
|
||||
map_location,
|
||||
class: "map",
|
||||
data: prepare_map_settings(map_location, editable, parent_class)
|
||||
map += map_location_remove_marker(map_location, remove_marker_label) if editable
|
||||
map
|
||||
end
|
||||
|
||||
def map_location_remove_marker(map_location, text)
|
||||
content_tag :div, class: "text-right" do
|
||||
content_tag :a,
|
||||
id: map_location_remove_marker_link_id(map_location),
|
||||
href: "#",
|
||||
class: "location-map-remove-marker-button delete" do
|
||||
text
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare_map_settings(map_location, editable, parent_class)
|
||||
options = {
|
||||
map: "",
|
||||
map_center_latitude: map_location_latitude(map_location),
|
||||
map_center_longitude: map_location_longitude(map_location),
|
||||
map_zoom: map_location_zoom(map_location),
|
||||
map_tiles_provider: Rails.application.secrets.map_tiles_provider,
|
||||
map_tiles_provider_attribution: Rails.application.secrets.map_tiles_provider_attribution,
|
||||
marker_editable: editable,
|
||||
marker_latitude: map_location.latitude,
|
||||
marker_longitude: map_location.longitude,
|
||||
marker_remove_selector: "##{map_location_remove_marker_link_id(map_location)}",
|
||||
latitude_input_selector: "##{map_location_input_id(parent_class, 'latitude')}",
|
||||
longitude_input_selector: "##{map_location_input_id(parent_class, 'longitude')}",
|
||||
zoom_input_selector: "##{map_location_input_id(parent_class, 'zoom')}"
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
module PollRecountsHelper
|
||||
|
||||
def total_recounts_by_booth(booth_assignment)
|
||||
booth_assignment.total_results.any? ? booth_assignment.total_results.to_a.sum(&:amount) : nil
|
||||
booth_assignment.recounts.any? ? booth_assignment.recounts.to_a.sum(&:total_amount) : nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -41,4 +41,12 @@ module PollsHelper
|
||||
booth.name + location
|
||||
end
|
||||
|
||||
def poll_voter_token(poll, user)
|
||||
Poll::Voter.where(poll: poll, user: user, origin: "web").first&.token || ''
|
||||
end
|
||||
|
||||
def voted_before_sign_in(question)
|
||||
question.answers.where(author: current_user).any? { |vote| current_user.current_sign_in_at >= vote.updated_at }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -32,8 +32,12 @@ module ProposalsHelper
|
||||
Proposal::RETIRE_OPTIONS.collect { |option| [ t("proposals.retire_options.#{option}"), option ] }
|
||||
end
|
||||
|
||||
def can_create_document?(document, proposal)
|
||||
can?(:create, document) && proposal.documents.size < Proposal.max_documents_allowed
|
||||
def empty_recommended_proposals_message_text(user)
|
||||
if user.interests.any?
|
||||
t('proposals.index.recommendations.without_results')
|
||||
else
|
||||
t('proposals.index.recommendations.without_interests')
|
||||
end
|
||||
end
|
||||
|
||||
def author_of_proposal?(proposal)
|
||||
@@ -44,4 +48,4 @@ module ProposalsHelper
|
||||
current_user && proposal.editable_by?(current_user)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
module ShiftsHelper
|
||||
|
||||
def shift_dates_select_options(polls)
|
||||
options = []
|
||||
(start_date(polls)..end_date(polls)).each do |date|
|
||||
options << [l(date, format: :long), l(date)]
|
||||
end
|
||||
options_for_select(options, params[:date])
|
||||
def shift_vote_collection_dates(polls)
|
||||
date_options((start_date(polls)..end_date(polls)))
|
||||
end
|
||||
|
||||
def shift_recount_scrutiny_dates(polls)
|
||||
date_options(polls.map(&:ends_at).map(&:to_date).sort.inject([]) { |total, date| total << (date..date + 1.week).to_a }.flatten.uniq)
|
||||
end
|
||||
|
||||
def date_options(dates)
|
||||
dates.map { |date| [l(date, format: :long), l(date)] }
|
||||
end
|
||||
|
||||
def start_date(polls)
|
||||
|
||||
62
app/helpers/welcome_helper.rb
Normal file
62
app/helpers/welcome_helper.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
module WelcomeHelper
|
||||
|
||||
def active_class(index)
|
||||
"is-active is-in" if index == 0
|
||||
end
|
||||
|
||||
def slide_display(index)
|
||||
"display: none;" if index > 0
|
||||
end
|
||||
|
||||
def recommended_path(recommended)
|
||||
case recommended.class.name
|
||||
when "Debate"
|
||||
debate_path(recommended)
|
||||
when "Proposal"
|
||||
proposal_path(recommended)
|
||||
else
|
||||
'#'
|
||||
end
|
||||
end
|
||||
|
||||
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_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
|
||||
end
|
||||
|
||||
def calculate_carousel_size(debates, proposals, apply_offset)
|
||||
offset = calculate_offset(debates, proposals, apply_offset)
|
||||
centered = calculate_centered(debates, proposals)
|
||||
"#{offset if offset} #{centered if centered}"
|
||||
end
|
||||
|
||||
def calculate_centered(debates, proposals)
|
||||
if (debates.blank? && proposals.any?) ||
|
||||
(debates.any? && proposals.blank?)
|
||||
centered = "medium-centered large-centered"
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_offset(debates, proposals, apply_offset)
|
||||
if (debates.any? && proposals.any?)
|
||||
if apply_offset
|
||||
offset = "medium-offset-2 large-offset-2"
|
||||
else
|
||||
offset = "end"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def highlight_background
|
||||
(feature?("user.recommendations") && current_user) ? "highlight" : ""
|
||||
end
|
||||
|
||||
end
|
||||
@@ -51,7 +51,7 @@ module Abilities
|
||||
|
||||
can [:read, :update, :valuate, :destroy, :summary], SpendingProposal
|
||||
|
||||
can [:index, :read, :new, :create, :update, :destroy, :calculate_winners], Budget
|
||||
can [:index, :read, :new, :create, :update, :destroy, :calculate_winners, :read_results], Budget
|
||||
can [:read, :create, :update, :destroy], Budget::Group
|
||||
can [:read, :create, :update, :destroy], Budget::Heading
|
||||
can [:hide, :update, :toggle_selection], Budget::Investment
|
||||
@@ -62,7 +62,7 @@ module Abilities
|
||||
|
||||
can [:index, :create, :edit, :update, :destroy], Geozone
|
||||
|
||||
can [:read, :create, :update, :destroy, :add_question, :remove_question, :search_booths, :search_questions, :search_officers], Poll
|
||||
can [:read, :create, :update, :destroy, :add_question, :search_booths, :search_officers], Poll
|
||||
can [:read, :create, :update, :destroy, :available], Poll::Booth
|
||||
can [:search, :create, :index, :destroy], ::Poll::Officer
|
||||
can [:create, :destroy], ::Poll::BoothAssignment
|
||||
@@ -81,6 +81,8 @@ module Abilities
|
||||
cannot :comment_as_moderator, [::Legislation::Question, Legislation::Annotation, ::Legislation::Proposal]
|
||||
|
||||
can [:create, :destroy], Document
|
||||
can [:destroy], Image
|
||||
can [:create, :destroy], DirectUpload
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +32,7 @@ module Abilities
|
||||
can :suggest, Debate
|
||||
can :suggest, Proposal
|
||||
can :suggest, Legislation::Proposal
|
||||
can :suggest, ActsAsTaggableOn::Tag
|
||||
|
||||
can [:flag, :unflag], Comment
|
||||
cannot [:flag, :unflag], Comment, user_id: user.id
|
||||
@@ -47,8 +48,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
|
||||
@@ -68,6 +72,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" }
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ class Budget
|
||||
include Reclassification
|
||||
include Followable
|
||||
include Communitable
|
||||
include Imageable
|
||||
include Mappable
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,10 @@ module Followable
|
||||
included do
|
||||
has_many :follows, as: :followable, dependent: :destroy
|
||||
has_many :followers, through: :follows, source: :user
|
||||
|
||||
scope :followed_by_user, -> (user){
|
||||
joins(:follows).where("follows.user_id = ?", user.id)
|
||||
}
|
||||
end
|
||||
|
||||
def followed_by?(user)
|
||||
|
||||
12
app/models/concerns/galleryable.rb
Normal file
12
app/models/concerns/galleryable.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module Galleryable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :images, as: :imageable, dependent: :destroy
|
||||
accepts_nested_attributes_for :images, allow_destroy: true, update_only: true
|
||||
|
||||
def image_url(style)
|
||||
image.attachment.url(style) if image && image.attachment.exists?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -24,7 +24,7 @@ module Graphqlable
|
||||
end
|
||||
|
||||
def graphql_type_description
|
||||
(model_name.human).to_s
|
||||
model_name.human.to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
14
app/models/concerns/imageable.rb
Normal file
14
app/models/concerns/imageable.rb
Normal 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
|
||||
9
app/models/concerns/mappable.rb
Normal file
9
app/models/concerns/mappable.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module Mappable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one :map_location, dependent: :destroy
|
||||
accepts_nested_attributes_for :map_location, allow_destroy: true
|
||||
end
|
||||
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -37,14 +37,21 @@ class Debate < ActiveRecord::Base
|
||||
scope :sort_by_random, -> { reorder("RANDOM()") }
|
||||
scope :sort_by_relevance, -> { all }
|
||||
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
|
||||
scope :sort_by_recommendations, -> { order(cached_votes_total: :desc) }
|
||||
scope :last_week, -> { where("created_at >= ?", 7.days.ago)}
|
||||
scope :featured, -> { where("featured_at is not null")}
|
||||
scope :public_for_api, -> { all }
|
||||
|
||||
# Ahoy setup
|
||||
visitable # Ahoy will automatically assign visit_id on create
|
||||
|
||||
attr_accessor :link_required
|
||||
|
||||
def self.recommendations(user)
|
||||
tagged_with(user.interests, any: true).
|
||||
where("author_id != ?", user.id)
|
||||
end
|
||||
|
||||
def searchable_values
|
||||
{ title => 'A',
|
||||
author.username => 'B',
|
||||
@@ -88,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
|
||||
@@ -135,4 +142,9 @@ class Debate < ActiveRecord::Base
|
||||
featured_at.present?
|
||||
end
|
||||
|
||||
def self.debates_orders(user)
|
||||
orders = %w{hot_score confidence_score created_at relevance}
|
||||
orders << "recommendations" if user.present?
|
||||
orders
|
||||
end
|
||||
end
|
||||
|
||||
66
app/models/direct_upload.rb
Normal file
66
app/models/direct_upload.rb
Normal file
@@ -0,0 +1,66 @@
|
||||
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)
|
||||
|
||||
#Refactor
|
||||
if @resource.respond_to?(:images) &&
|
||||
((@attachment.present? && !@attachment.content_type.match(/pdf/)) || @cached_attachment.present?)
|
||||
@relation = @resource.images.send("build", relation_attributtes)
|
||||
elsif @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
|
||||
@@ -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
112
app/models/image.rb
Normal 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
|
||||
10
app/models/map_location.rb
Normal file
10
app/models/map_location.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class MapLocation < ActiveRecord::Base
|
||||
|
||||
belongs_to :proposal
|
||||
belongs_to :investment, class_name: Budget::Investment
|
||||
|
||||
def available?
|
||||
latitude.present? && longitude.present? && zoom.present?
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,10 +1,10 @@
|
||||
class Poll < ActiveRecord::Base
|
||||
include Imageable
|
||||
|
||||
has_many :booth_assignments, class_name: "Poll::BoothAssignment"
|
||||
has_many :booths, through: :booth_assignments
|
||||
has_many :partial_results, through: :booth_assignments
|
||||
has_many :white_results, through: :booth_assignments
|
||||
has_many :null_results, through: :booth_assignments
|
||||
has_many :total_results, through: :booth_assignments
|
||||
has_many :recounts, through: :booth_assignments
|
||||
has_many :voters
|
||||
has_many :officer_assignments, through: :booth_assignments
|
||||
has_many :officers, through: :officer_assignments
|
||||
@@ -16,23 +16,23 @@ class Poll < ActiveRecord::Base
|
||||
|
||||
validate :date_range
|
||||
|
||||
scope :current, -> { where('starts_at <= ? and ? <= ends_at', Time.current, Time.current) }
|
||||
scope :incoming, -> { where('? < starts_at', Time.current) }
|
||||
scope :expired, -> { where('ends_at < ?', Time.current) }
|
||||
scope :current, -> { where('starts_at <= ? and ? <= ends_at', Date.current.beginning_of_day, Date.current.beginning_of_day) }
|
||||
scope :incoming, -> { where('? < starts_at', Date.current.beginning_of_day) }
|
||||
scope :expired, -> { where('ends_at < ?', Date.current.beginning_of_day) }
|
||||
scope :published, -> { where('published = ?', true) }
|
||||
scope :by_geozone_id, ->(geozone_id) { where(geozones: {id: geozone_id}.joins(:geozones)) }
|
||||
|
||||
scope :sort_for_list, -> { order(:geozone_restricted, :starts_at, :name) }
|
||||
|
||||
def current?(timestamp = DateTime.current)
|
||||
def current?(timestamp = Date.current.beginning_of_day)
|
||||
starts_at <= timestamp && timestamp <= ends_at
|
||||
end
|
||||
|
||||
def incoming?(timestamp = DateTime.current)
|
||||
def incoming?(timestamp = Date.current.beginning_of_day)
|
||||
timestamp < starts_at
|
||||
end
|
||||
|
||||
def expired?(timestamp = DateTime.current)
|
||||
def expired?(timestamp = Date.current.beginning_of_day)
|
||||
ends_at < timestamp
|
||||
end
|
||||
|
||||
@@ -61,6 +61,10 @@ class Poll < ActiveRecord::Base
|
||||
voters.where(document_number: document_number, document_type: document_type).exists?
|
||||
end
|
||||
|
||||
def voted_in_booth?(user)
|
||||
Poll::Voter.where(poll: self, user: user, origin: "booth").exists?
|
||||
end
|
||||
|
||||
def date_range
|
||||
unless starts_at.present? && ends_at.present? && starts_at <= ends_at
|
||||
errors.add(:starts_at, I18n.t('errors.messages.invalid_date_range'))
|
||||
|
||||
@@ -8,12 +8,15 @@ class Poll::Answer < ActiveRecord::Base
|
||||
validates :question, presence: true
|
||||
validates :author, presence: true
|
||||
validates :answer, presence: true
|
||||
validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }}
|
||||
|
||||
# temporary skipping validation, review when removing valid_answers
|
||||
# validates :answer, inclusion: { in: ->(a) { a.question.valid_answers }},
|
||||
# unless: ->(a) { a.question.blank? }
|
||||
|
||||
scope :by_author, ->(author_id) { where(author_id: author_id) }
|
||||
scope :by_question, ->(question_id) { where(question_id: question_id) }
|
||||
|
||||
def record_voter_participation
|
||||
Poll::Voter.create!(user: author, poll: poll)
|
||||
def record_voter_participation(token)
|
||||
Poll::Voter.find_or_create_by(user: author, poll: poll, origin: "web", token: token)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,8 +7,6 @@ class Poll
|
||||
has_many :officers, through: :officer_assignments
|
||||
has_many :voters
|
||||
has_many :partial_results
|
||||
has_many :white_results
|
||||
has_many :null_results
|
||||
has_many :total_results
|
||||
has_many :recounts
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@ class Poll
|
||||
class Officer < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
has_many :officer_assignments, class_name: "Poll::OfficerAssignment"
|
||||
has_many :shifts, class_name: "Poll::Shift"
|
||||
has_many :failed_census_calls, foreign_key: :poll_officer_id
|
||||
|
||||
validates :user_id, presence: true, uniqueness: true
|
||||
|
||||
@@ -3,14 +3,12 @@ class Poll
|
||||
belongs_to :officer
|
||||
belongs_to :booth_assignment
|
||||
has_many :partial_results
|
||||
has_many :white_results
|
||||
has_many :null_results
|
||||
has_many :total_results
|
||||
has_many :recounts
|
||||
has_many :voters
|
||||
|
||||
validates :officer_id, presence: true
|
||||
validates :booth_assignment_id, presence: true
|
||||
validates :date, presence: true, uniqueness: { scope: [:officer_id, :booth_assignment_id] }
|
||||
validates :date, presence: true
|
||||
|
||||
delegate :poll_id, :booth_id, to: :booth_assignment
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
class Poll::Question < ActiveRecord::Base
|
||||
include Measurable
|
||||
include Searchable
|
||||
include Documentable
|
||||
documentable max_documents_allowed: 1,
|
||||
max_file_size: 3.megabytes,
|
||||
accepted_content_types: [ "application/pdf" ]
|
||||
accepts_nested_attributes_for :documents, allow_destroy: true
|
||||
|
||||
acts_as_paranoid column: :hidden_at
|
||||
include ActsAsParanoidAliases
|
||||
@@ -14,7 +9,8 @@ class Poll::Question < ActiveRecord::Base
|
||||
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
|
||||
|
||||
has_many :comments, as: :commentable
|
||||
has_many :answers
|
||||
has_many :answers, class_name: 'Poll::Answer'
|
||||
has_many :question_answers, class_name: 'Poll::Question::Answer'
|
||||
has_many :partial_results
|
||||
belongs_to :proposal
|
||||
|
||||
@@ -23,7 +19,6 @@ class Poll::Question < ActiveRecord::Base
|
||||
validates :poll_id, presence: true
|
||||
|
||||
validates :title, length: { minimum: 4 }
|
||||
validates :description, length: { maximum: Poll::Question.description_max_length }
|
||||
|
||||
scope :by_poll_id, ->(poll_id) { where(poll_id: poll_id) }
|
||||
|
||||
@@ -40,15 +35,10 @@ class Poll::Question < ActiveRecord::Base
|
||||
def searchable_values
|
||||
{ title => 'A',
|
||||
proposal.try(:title) => 'A',
|
||||
description => 'B',
|
||||
author.username => 'C',
|
||||
author_visible_name => 'C' }
|
||||
end
|
||||
|
||||
def description
|
||||
super.try :html_safe
|
||||
end
|
||||
|
||||
def valid_answers
|
||||
(super.try(:split, ',').compact || []).map(&:strip)
|
||||
end
|
||||
@@ -59,7 +49,6 @@ class Poll::Question < ActiveRecord::Base
|
||||
self.author_visible_name = proposal.author.name
|
||||
self.proposal_id = proposal.id
|
||||
self.title = proposal.title
|
||||
self.description = proposal.description
|
||||
self.valid_answers = I18n.t('poll_questions.default_valid_answers')
|
||||
end
|
||||
end
|
||||
|
||||
17
app/models/poll/question/answer.rb
Normal file
17
app/models/poll/question/answer.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class Poll::Question::Answer < ActiveRecord::Base
|
||||
include Galleryable
|
||||
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
|
||||
|
||||
belongs_to :question, class_name: 'Poll::Question', foreign_key: 'question_id'
|
||||
has_many :videos, class_name: 'Poll::Question::Answer::Video'
|
||||
|
||||
validates :title, presence: true
|
||||
|
||||
def description
|
||||
super.try :html_safe
|
||||
end
|
||||
end
|
||||
16
app/models/poll/question/answer/video.rb
Normal file
16
app/models/poll/question/answer/video.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
class Poll::Question::Answer::Video < ActiveRecord::Base
|
||||
belongs_to :answer, class_name: 'Poll::Question::Answer', foreign_key: 'answer_id'
|
||||
|
||||
VIMEO_REGEX = /vimeo.*(staffpicks\/|channels\/|videos\/|video\/|\/)([^#\&\?]*).*/
|
||||
YOUTUBE_REGEX = /youtu.*(be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
|
||||
|
||||
validates :title, presence: true
|
||||
validate :valid_url?
|
||||
|
||||
def valid_url?
|
||||
return if url.blank?
|
||||
return if url.match(VIMEO_REGEX)
|
||||
return if url.match(YOUTUBE_REGEX)
|
||||
errors.add(:url, :invalid)
|
||||
end
|
||||
end
|
||||
36
app/models/poll/recount.rb
Normal file
36
app/models/poll/recount.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class Poll::Recount < ActiveRecord::Base
|
||||
|
||||
VALID_ORIGINS = %w{web booth letter}.freeze
|
||||
|
||||
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
|
||||
belongs_to :booth_assignment
|
||||
belongs_to :officer_assignment
|
||||
|
||||
validates :author, presence: true
|
||||
validates :origin, inclusion: {in: VALID_ORIGINS}
|
||||
|
||||
scope :web, -> { where(origin: 'web') }
|
||||
scope :booth, -> { where(origin: 'booth') }
|
||||
scope :letter, -> { where(origin: 'letter') }
|
||||
|
||||
scope :by_author, ->(author_id) { where(author_id: author_id) }
|
||||
|
||||
before_save :update_logs
|
||||
|
||||
def update_logs
|
||||
amounts_changed = false
|
||||
|
||||
[:white, :null, :total].each do |amount|
|
||||
next unless send("#{amount}_amount_changed?") && send("#{amount}_amount_was").present?
|
||||
self["#{amount}_amount_log"] += ":#{send("#{amount}_amount_was")}"
|
||||
amounts_changed = true
|
||||
end
|
||||
|
||||
update_officer_author if amounts_changed
|
||||
end
|
||||
|
||||
def update_officer_author
|
||||
self.officer_assignment_id_log += ":#{officer_assignment_id_was}"
|
||||
self.author_id_log += ":#{author_id_was}"
|
||||
end
|
||||
end
|
||||
@@ -5,25 +5,41 @@ class Poll
|
||||
|
||||
validates :booth_id, presence: true
|
||||
validates :officer_id, presence: true
|
||||
validates :date, presence: true
|
||||
validates :date, uniqueness: { scope: [:officer_id, :booth_id] }
|
||||
validates :date, presence: true, uniqueness: { scope: [:officer_id, :booth_id, :task] }
|
||||
validates :task, presence: true
|
||||
|
||||
enum task: { vote_collection: 0, recount_scrutiny: 1 }
|
||||
|
||||
scope :vote_collection, -> { where(task: 'vote_collection') }
|
||||
scope :recount_scrutiny, -> { where(task: 'recount_scrutiny') }
|
||||
scope :current, -> { where(date: Date.current) }
|
||||
|
||||
before_create :persist_data
|
||||
after_create :create_officer_assignments
|
||||
|
||||
def create_officer_assignments
|
||||
booth.booth_assignments.each do |booth_assignment|
|
||||
attrs = { officer_id: officer_id,
|
||||
date: date,
|
||||
booth_assignment_id: booth_assignment.id }
|
||||
Poll::OfficerAssignment.create!(attrs)
|
||||
end
|
||||
end
|
||||
before_destroy :destroy_officer_assignments
|
||||
|
||||
def persist_data
|
||||
self.officer_name = officer.name
|
||||
self.officer_email = officer.email
|
||||
end
|
||||
|
||||
def create_officer_assignments
|
||||
booth.booth_assignments.each do |booth_assignment|
|
||||
attrs = {
|
||||
officer_id: officer_id,
|
||||
date: date,
|
||||
booth_assignment_id: booth_assignment.id,
|
||||
final: recount_scrutiny?
|
||||
}
|
||||
Poll::OfficerAssignment.create!(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_officer_assignments
|
||||
Poll::OfficerAssignment.where(booth_assignment: booth.booth_assignments,
|
||||
officer: officer,
|
||||
date: date,
|
||||
final: recount_scrutiny?).destroy_all
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
class Poll
|
||||
class Voter < ActiveRecord::Base
|
||||
|
||||
VALID_ORIGINS = %w{ web booth }
|
||||
|
||||
belongs_to :poll
|
||||
belongs_to :user
|
||||
belongs_to :geozone
|
||||
belongs_to :booth_assignment
|
||||
belongs_to :officer_assignment
|
||||
belongs_to :officer
|
||||
|
||||
validates :poll_id, presence: true
|
||||
validates :user_id, presence: true
|
||||
|
||||
validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted }
|
||||
validates :origin, inclusion: { in: VALID_ORIGINS }
|
||||
|
||||
before_validation :set_demographic_info, :set_document_info
|
||||
|
||||
scope :web, -> { where(origin: 'web') }
|
||||
scope :booth, -> { where(origin: 'booth') }
|
||||
|
||||
def set_demographic_info
|
||||
return if user.blank?
|
||||
|
||||
|
||||
@@ -10,11 +10,12 @@ class Proposal < ActiveRecord::Base
|
||||
include Graphqlable
|
||||
include Followable
|
||||
include Communitable
|
||||
include Imageable
|
||||
include Mappable
|
||||
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
|
||||
@@ -57,14 +58,27 @@ class Proposal < ActiveRecord::Base
|
||||
scope :sort_by_relevance, -> { all }
|
||||
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
|
||||
scope :sort_by_archival_date, -> { archived.sort_by_confidence_score }
|
||||
scope :sort_by_recommendations, -> { order(cached_votes_up: :desc) }
|
||||
scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago) }
|
||||
scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago) }
|
||||
scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)}
|
||||
scope :retired, -> { where.not(retired_at: nil) }
|
||||
scope :not_retired, -> { where(retired_at: nil) }
|
||||
scope :successful, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success) }
|
||||
scope :unsuccessful, -> { where("cached_votes_up < ?", Proposal.votes_needed_for_success) }
|
||||
scope :public_for_api, -> { all }
|
||||
|
||||
def self.recommendations(user)
|
||||
tagged_with(user.interests, any: true).
|
||||
where("author_id != ?", user.id).
|
||||
unsuccessful.
|
||||
not_followed_by_user(user)
|
||||
end
|
||||
|
||||
def self.not_followed_by_user(user)
|
||||
where.not(id: followed_by_user(user).pluck(:id))
|
||||
end
|
||||
|
||||
def to_param
|
||||
"#{id}-#{title}".parameterize
|
||||
end
|
||||
@@ -88,7 +102,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)
|
||||
@@ -184,6 +198,12 @@ class Proposal < ActiveRecord::Base
|
||||
(voters + followers).uniq
|
||||
end
|
||||
|
||||
def self.proposals_orders(user)
|
||||
orders = %w{hot_score confidence_score created_at relevance archival_date}
|
||||
orders << "recommendations" if user.present?
|
||||
orders
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def set_responsible_name
|
||||
|
||||
@@ -88,12 +88,12 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def debate_votes(debates)
|
||||
voted = votes.for_debates(debates)
|
||||
voted = votes.for_debates(Array(debates).map(&:id))
|
||||
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
|
||||
end
|
||||
|
||||
def proposal_votes(proposals)
|
||||
voted = votes.for_proposals(proposals)
|
||||
voted = votes.for_proposals(Array(proposals).map(&:id))
|
||||
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
|
||||
end
|
||||
|
||||
|
||||
@@ -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="#">
|
||||
@@ -53,19 +53,20 @@
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% if feature?(:polls) %>
|
||||
<li class="section-title">
|
||||
<a href="#">
|
||||
<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::Questions::Answers %>>
|
||||
<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 controller_name == "questions" ||
|
||||
controller_name == "answers" ||
|
||||
controller.class.parent == Admin::Poll::Questions::Answers %>>
|
||||
<%= link_to t("admin.menu.poll_questions"), admin_questions_path %>
|
||||
</li>
|
||||
|
||||
@@ -158,12 +159,14 @@
|
||||
<span class="icon-settings"></span>
|
||||
<strong><%= t("admin.menu.title_site_customization") %></strong>
|
||||
</a>
|
||||
<ul <%= "class=is-active" if menu_customization? %>>
|
||||
<ul <%= "class=is-active" if menu_customization? &&
|
||||
controller.class.parent != Admin::Poll::Questions::Answers %>>
|
||||
<li <%= "class=active" if controller_name == "pages" %>>
|
||||
<%= link_to t("admin.menu.site_customization.pages"), admin_site_customization_pages_path %>
|
||||
</li>
|
||||
|
||||
<li <%= "class=active" if controller_name == "images" %>>
|
||||
<li <%= "class=active" if controller_name == "images" &&
|
||||
controller.class.parent != Admin::Poll::Questions::Answers %>>
|
||||
<%= link_to t("admin.menu.site_customization.images"), admin_site_customization_images_path %>
|
||||
</li>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -14,15 +14,15 @@
|
||||
|
||||
<div class="tabs-content" data-tabs-content="booths-tabs">
|
||||
<ul class="tabs" data-tabs id="booths-tabs">
|
||||
<li class="tabs-title is-active">
|
||||
<li class="tabs-title">
|
||||
<%= link_to t("admin.poll_booth_assignments.show.officers"), "#tab-officers" %>
|
||||
</li>
|
||||
<li class="tabs-title">
|
||||
<li class="tabs-title is-active">
|
||||
<%= link_to t("admin.poll_booth_assignments.show.recounts"), "#tab-recounts" %>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tabs-panel is-active" id="tab-officers">
|
||||
<div class="tabs-panel" id="tab-officers">
|
||||
<% if @booth_assignment.officers.empty? %>
|
||||
<div class="callout primary margin-top">
|
||||
<%= t("admin.poll_booth_assignments.show.no_officers") %>
|
||||
@@ -43,27 +43,36 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="tabs-panel" id="tab-recounts">
|
||||
<div class="tabs-panel is-active" 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.total_recount") %></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| %>
|
||||
<% total_recount = @booth_assignment.total_results.where(date: voting_date).first %>
|
||||
<% 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 total_recount.present? %>
|
||||
<td class="text-center <%= 'count-error' if total_recount.amount != system_count %>" title="<%= total_recount.officer_assignment.officer.name %>"><%= total_recount.amount %></td>
|
||||
<% else %>
|
||||
<td class="text-center" title=""> - </td>
|
||||
<% end %>
|
||||
<td class="text-center"><%= system_count %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user