diff --git a/Gemfile b/Gemfile index 05d1379c5..a5ec32008 100644 --- a/Gemfile +++ b/Gemfile @@ -66,6 +66,9 @@ gem 'turnout', '~> 2.4.0' gem 'redcarpet', '~> 3.4.0' gem 'paperclip' +gem 'rails-assets-markdown-it', source: 'https://rails-assets.org' + +gem 'cocoon' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 0989276ef..b1540cfe1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,5 +1,6 @@ GEM remote: https://rubygems.org/ + remote: https://rails-assets.org/ specs: actionmailer (4.2.8) actionpack (= 4.2.8) @@ -101,6 +102,7 @@ GEM cliver (0.3.2) cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) + cocoon (1.2.9) coffee-rails (4.2.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.2.x) @@ -318,6 +320,7 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.2.8) sprockets-rails + rails-assets-markdown-it (8.2.1) rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) rails-dom-testing (1.0.8) @@ -482,6 +485,7 @@ DEPENDENCIES capistrano3-delayed-job (~> 1.7.3) capybara (~> 2.14.0) ckeditor (~> 4.2.3) + cocoon coffee-rails (~> 4.2.1) coveralls (~> 0.8.21) daemons @@ -519,6 +523,7 @@ DEPENDENCIES poltergeist (~> 1.15.0) quiet_assets rails (= 4.2.8) + rails-assets-markdown-it! redcarpet (~> 3.4.0) responders (~> 2.4.0) rinku (~> 2.0.2) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 928f06edd..d736be793 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -50,6 +50,15 @@ //= require banners //= require social_share //= require checkbox_toggle +//= require markdown-it +//= require markdown_editor +//= require cocoon +//= require legislation_admin +//= require legislation +//= require legislation_allegations +//= require legislation_annotatable +//= require watch_form_changes +//= require tree_navigator //= require custom var initialize_modules = function() { @@ -76,6 +85,14 @@ var initialize_modules = function() { App.Banners.initialize(); App.SocialShare.initialize(); App.CheckboxToggle.initialize(); + App.MarkdownEditor.initialize(); + App.LegislationAdmin.initialize(); + App.LegislationAllegations.initialize(); + App.Legislation.initialize(); + if ( $(".legislation-annotatable").length ) + App.LegislationAnnotatable.initialize(); + App.WatchFormChanges.initialize(); + App.TreeNavigator.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/legislation.js.coffee b/app/assets/javascripts/legislation.js.coffee new file mode 100644 index 000000000..3eb53e6d2 --- /dev/null +++ b/app/assets/javascripts/legislation.js.coffee @@ -0,0 +1,25 @@ +App.Legislation = + + initialize: -> + $('#js-toggle-debate').on + click: -> + $('#debate-show').toggle() + + $('#js-toggle-small-debate').on + click: -> + $('#debate-show').toggle() + $('span').toggleClass('icon-angle-up') + + $('form#new_legislation_answer input.button').hide() + $('form#new_legislation_answer input[type=radio]').on + click: -> + $('form#new_legislation_answer').submit() + + $('form#draft_version_go_to_version input.button').hide() + $('form#draft_version_go_to_version select').on + change: -> + $('form#draft_version_go_to_version').submit() + + $('#js-toggle-legislation-process-header').on + click: -> + $('[data-target="legislation-header-full"]').toggle() diff --git a/app/assets/javascripts/legislation_admin.js.coffee b/app/assets/javascripts/legislation_admin.js.coffee new file mode 100644 index 000000000..c61d9cea1 --- /dev/null +++ b/app/assets/javascripts/legislation_admin.js.coffee @@ -0,0 +1,14 @@ +App.LegislationAdmin = + + initialize: -> + $("input[type='checkbox'][data-disable-date]").on + change: -> + checkbox = $(this) + parent = $(this).parents('.row:eq(0)') + date_selector = $(this).data('disable-date') + parent.find("input[type='text'][id^='"+date_selector+"']").each -> + if checkbox.is(':checked') + $(this).removeAttr("disabled") + else + $(this).val("") + diff --git a/app/assets/javascripts/legislation_allegations.js.coffee b/app/assets/javascripts/legislation_allegations.js.coffee new file mode 100644 index 000000000..e5c8ea62f --- /dev/null +++ b/app/assets/javascripts/legislation_allegations.js.coffee @@ -0,0 +1,25 @@ +App.LegislationAllegations = + + toggle_comments: -> + if !App.LegislationAnnotatable.isMobile() + $('.draft-allegation').toggleClass('comments-on') + $('#comments-box').html('').hide() + + show_comments: -> + if !App.LegislationAnnotatable.isMobile() + $('.draft-allegation').addClass('comments-on') + + initialize: -> + $('.js-toggle-allegations .draft-panel').on + click: (e) -> + e.preventDefault() + e.stopPropagation() + if !App.LegislationAnnotatable.isMobile() + App.LegislationAllegations.toggle_comments() + + $('.js-toggle-allegations').on + click: (e) -> + # Toggle comments when the section title is visible + if !App.LegislationAnnotatable.isMobile() + if $(this).find('.draft-panel .panel-title:visible').length == 0 + App.LegislationAllegations.toggle_comments() diff --git a/app/assets/javascripts/legislation_annotatable.js.coffee b/app/assets/javascripts/legislation_annotatable.js.coffee new file mode 100644 index 000000000..6b7e2e603 --- /dev/null +++ b/app/assets/javascripts/legislation_annotatable.js.coffee @@ -0,0 +1,215 @@ +_t = (key) -> new Gettext().gettext(key) + +App.LegislationAnnotatable = + + makeEditableAndHighlight: (colour) -> + sel = window.getSelection() + if sel.rangeCount and sel.getRangeAt + range = sel.getRangeAt(0) + document.designMode = 'on' + if range + sel.removeAllRanges() + sel.addRange range + # Use HiliteColor since some browsers apply BackColor to the whole block + if !document.execCommand('HiliteColor', false, colour) + document.execCommand 'BackColor', false, colour + document.designMode = 'off' + return + + highlight: (colour) -> + if window.getSelection + # IE9 and non-IE + try + if !document.execCommand('BackColor', false, colour) + App.LegislationAnnotatable.makeEditableAndHighlight colour + catch ex + App.LegislationAnnotatable.makeEditableAndHighlight colour + else if document.selection and document.selection.createRange + # IE <= 8 case + range = document.selection.createRange() + range.execCommand 'BackColor', false, colour + return + + remove_highlight: -> + $('[data-legislation-draft-version-id] span[style]').replaceWith(-> + return $(this).contents() + ) + return + + renderAnnotationComments: (event) -> + if event.offset + $("#comments-box").css({top: event.offset - $('.calc-comments').offset().top}) + + if App.LegislationAnnotatable.isMobile() + return + + $.ajax + method: "GET" + url: event.annotation_url + "/annotations/" + event.annotation_id + "/comments" + dataType: 'script' + + onClick: (event) -> + event.preventDefault() + event.stopPropagation() + + if App.LegislationAnnotatable.isMobile() + annotation_url = $(event.target).closest(".legislation-annotatable").data("legislation-annotatable-base-url") + window.location.href = annotation_url + "/annotations/" + $(this).data('annotation-id') + return + + $('[data-annotation-id]').removeClass('current-annotation') + + target = $(this) + + parents = target.parents('.annotator-hl') + parents_ids = parents.map (_, elem) -> + $(elem).data("annotation-id") + + annotation_id = target.data('annotation-id') + $('[data-annotation-id="'+annotation_id+'"]').addClass('current-annotation') + + $('#comments-box').html('') + App.LegislationAllegations.show_comments() + $("#comments-box").show() + + $.event.trigger + type: "renderLegislationAnnotation" + annotation_id: target.data("annotation-id") + annotation_url: target.closest(".legislation-annotatable").data("legislation-annotatable-base-url") + offset: target.offset()["top"] + + parents_ids.each (i, pid) -> + $.event.trigger + type: "renderLegislationAnnotation" + annotation_id: pid + annotation_url: target.closest(".legislation-annotatable").data("legislation-annotatable-base-url") + + isMobile: () -> + return window.innerWidth <= 652 + + viewerExtension: (viewer) -> + viewer._onHighlightMouseover = (event) -> + return + + customShow: (position) -> + $(@element).html '' + # Clean comments section and open it + $('#comments-box').html '' + App.LegislationAllegations.show_comments() + $('#comments-box').show() + + annotation_url = $('[data-legislation-annotatable-base-url]').data('legislation-annotatable-base-url') + $.ajax( + method: 'GET' + url: annotation_url + '/annotations/new' + dataType: 'script').done (-> + $('#new_legislation_annotation #legislation_annotation_quote').val(@annotation.quote) + $('#new_legislation_annotation #legislation_annotation_ranges').val(JSON.stringify(@annotation.ranges)) + $('#comments-box').css({top: position.top - $('.calc-comments').offset().top}) + + unless $('[data-legislation-open-phase]').data('legislation-open-phase') == false + App.LegislationAnnotatable.highlight('#7fff9a') + $('#comments-box textarea').focus() + + $("#new_legislation_annotation").on("ajax:complete", (e, data, status, xhr) -> + App.LegislationAnnotatable.app.destroy() + if data.status == 200 + App.LegislationAnnotatable.remove_highlight() + $("#comments-box").html("").hide() + $.ajax + method: "GET" + url: annotation_url + "/annotations/" + data.responseJSON.id + "/comments" + dataType: 'script' + else + $(e.target).find('label').addClass('error') + $('' + data.responseJSON[0] + '').insertAfter($(e.target).find('textarea')) + return true + ) + return + ).bind(this) + + editorExtension: (editor) -> + editor.show = App.LegislationAnnotatable.customShow + + scrollToAnchor: -> + annotationsLoaded: (annotations) -> + anchor = $(location).attr('hash') + if anchor && anchor.startsWith('#annotation') + ann_id = anchor.split("-")[-1..] + + checkExist = setInterval((-> + if $("span[data-annotation-id='" + ann_id + "']").length + el = $("span[data-annotation-id='" + ann_id + "']") + el.addClass('current-annotation') + $('#comments-box').html('') + App.LegislationAllegations.show_comments() + $('html,body').animate({scrollTop: el.offset().top}) + $.event.trigger + type: "renderLegislationAnnotation" + annotation_id: ann_id + annotation_url: el.closest(".legislation-annotatable").data("legislation-annotatable-base-url") + offset: el.offset()["top"] + clearInterval checkExist + return + ), 100) + + propotionalWeight: (v, max) -> + Math.floor(v*5/(max+1)) + 1 + + addWeightClasses: -> + annotationsLoaded: (annotations) -> + return if annotations.length == 0 + weights = annotations.map (ann) -> ann.weight + max_weight = Math.max.apply(null, weights) + last_annotation = annotations[annotations.length - 1] + + checkExist = setInterval((-> + if $("span[data-annotation-id='" + last_annotation.id + "']").length + for annotation in annotations + ann_weight = App.LegislationAnnotatable.propotionalWeight(annotation.weight, max_weight) + el = $("span[data-annotation-id='" + annotation.id + "']") + el.addClass('weight-' + ann_weight) + clearInterval checkExist + return + ), 100) + + initialize: -> + $(document).off("renderLegislationAnnotation").on("renderLegislationAnnotation", App.LegislationAnnotatable.renderAnnotationComments) + $(document).off('click', '[data-annotation-id]').on('click', '[data-annotation-id]', App.LegislationAnnotatable.onClick) + $(document).off('click', '[data-cancel-annotation]').on('click', '[data-cancel-annotation]', (e) -> + e.preventDefault() + $('#comments-box').html('') + $('#comments-box').hide() + App.LegislationAnnotatable.remove_highlight() + return + ) + + current_user_id = $('html').data('current-user-id') + + $(".legislation-annotatable").each -> + $this = $(this) + ann_type = "legislation_draft_version" + ann_id = $this.data("legislation-draft-version-id") + base_url = $this.data("legislation-annotatable-base-url") + + App.LegislationAnnotatable.app = new annotator.App() + .include -> + beforeAnnotationCreated: (ann) -> + ann["legislation_draft_version_id"] = ann_id + ann.permissions = ann.permissions || {} + ann.permissions.admin = [] + .include(annotator.ui.main, { + element: this, + viewerExtensions: [App.LegislationAnnotatable.viewerExtension], + editorExtensions: [App.LegislationAnnotatable.editorExtension] + }) + .include(App.LegislationAnnotatable.scrollToAnchor) + .include(App.LegislationAnnotatable.addWeightClasses) + .include(annotator.storage.http, { prefix: base_url, urls: { search: "/annotations/search" } }) + + App.LegislationAnnotatable.app.start().then -> + App.LegislationAnnotatable.app.ident.identity = current_user_id + + options = {} + options["legislation_draft_version_id"] = ann_id + App.LegislationAnnotatable.app.annotations.load(options) diff --git a/app/assets/javascripts/markdown_editor.js.coffee b/app/assets/javascripts/markdown_editor.js.coffee new file mode 100644 index 000000000..510bf83ec --- /dev/null +++ b/app/assets/javascripts/markdown_editor.js.coffee @@ -0,0 +1,39 @@ +App.MarkdownEditor = + + refresh_preview: (element, md) -> + textarea_content = element.find('textarea').val() + result = md.render(textarea_content) + element.find('#markdown-preview').html(result) + + initialize: -> + $('.markdown-editor').each -> + md = window.markdownit({ + html: true, + breaks: true, + typographer: true, + }) + + App.MarkdownEditor.refresh_preview($(this), md) + + $(this).on 'change input paste keyup', -> + App.MarkdownEditor.refresh_preview($(this), md) + $('.legislation-draft-versions-edit .warning').show() + return + + $(this).find('textarea').on 'scroll', -> + $('#markdown-preview').scrollTop($(this).scrollTop()) + + $(this).find('.fullscreen-toggle').on 'click', -> + $('.markdown-editor').toggleClass('fullscreen') + $('.fullscreen-container').toggleClass('medium-8', 'medium-12') + span = $(this).find('span') + current_html = span.html() + if(current_html == span.data('open-text')) + span.html(span.data('closed-text')) + else + span.html(span.data('open-text')) + + if $('.markdown-editor').hasClass('fullscreen') + $('.markdown-editor textarea').height($(window).height() - 100) + else + $('.markdown-editor textarea').height("10em") diff --git a/app/assets/javascripts/tree_navigator.js.coffee b/app/assets/javascripts/tree_navigator.js.coffee new file mode 100644 index 000000000..1fdc50ba2 --- /dev/null +++ b/app/assets/javascripts/tree_navigator.js.coffee @@ -0,0 +1,37 @@ +App.TreeNavigator = + setNodes: (nodes) -> + children = nodes.children('ul') + + if(children.length == 0) + return + + children.each -> + link = $(this).prev('a') + $('').insertBefore(link) + App.TreeNavigator.setNodes($(this).children()) + + initialize: -> + elem = $('[data-tree-navigator]') + if(elem.length == 0) + return + + ul = elem.find('ul:eq(0)') + if(ul.length && ul.children().length) + App.TreeNavigator.setNodes(ul.children()) + + $('[data-tree-navigator] span').on + click: (e) -> + elem = $(this) + if(elem.hasClass('open')) + elem.removeClass('open').addClass('closed') + elem.siblings('ul').hide() + else if(elem.hasClass('closed')) + elem.removeClass('closed').addClass('open') + elem.siblings('ul').show() + + if anchor = $(location).attr('hash') + if link = elem.find('a[href="'+anchor+'"]') + link.parents('ul').each -> + $(this).show() + $(this).siblings('span').removeClass('closed').addClass('open') + diff --git a/app/assets/javascripts/votes.js.coffee b/app/assets/javascripts/votes.js.coffee index b842c3ffc..0f6dfcfc5 100644 --- a/app/assets/javascripts/votes.js.coffee +++ b/app/assets/javascripts/votes.js.coffee @@ -13,4 +13,6 @@ App.Votes = initialize: -> App.Votes.hoverize "div.votes" App.Votes.hoverize "div.supports" + App.Votes.hoverize "div.debate-questions" + App.Votes.hoverize "div.comment-footer" false diff --git a/app/assets/javascripts/watch_form_changes.js.coffee b/app/assets/javascripts/watch_form_changes.js.coffee new file mode 100644 index 000000000..eaf125ded --- /dev/null +++ b/app/assets/javascripts/watch_form_changes.js.coffee @@ -0,0 +1,30 @@ +App.WatchFormChanges = + forms: -> + return $('form[data-watch-changes]') + + msg: -> + if($('[data-watch-form-message]').length) + return $('[data-watch-form-message]').data('watch-form-message') + + checkChanges: (event) -> + changes = false + App.WatchFormChanges.forms().each -> + form = $(this) + if form.serialize() != form.data('watchChanges') + changes = true + if changes + return confirm(App.WatchFormChanges.msg()) + else + return true + + initialize: -> + if App.WatchFormChanges.forms().length == 0 || App.WatchFormChanges.msg() == undefined + return + + $(document).off('page:before-change').on('page:before-change', (e) -> App.WatchFormChanges.checkChanges(e)) + + App.WatchFormChanges.forms().each -> + form = $(this) + form.data('watchChanges', form.serialize()) + + false diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss index b804d62ba..6ae95791b 100644 --- a/app/assets/stylesheets/_settings.scss +++ b/app/assets/stylesheets/_settings.scss @@ -164,6 +164,7 @@ $header-font-family: $body-font-family; $header-font-weight: $global-weight-normal; $header-font-style: normal; $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace; +$font-family-serif: Georgia, 'Times New Roman', Times, serif; $header-sizes: ( small: ( 'h1': 34, diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index fd6ed0815..13f946cde 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -6,6 +6,8 @@ // 04. Stats // 05. Management // 06. Polls +// 07. Legislation +// 08. CMS // // 01. Global styles @@ -538,7 +540,75 @@ table { } } -// 07. CMS +// 07. Legislation +// -------------- + +// Markdown Editor +// --------------- + +.markdown-editor { + background-color: white; + + .markdown-area, + #markdown-preview { + display: none; + } +} + +.markdown-editor #markdown-preview { + overflow-y: auto; + height: 15em; +} + +.markdown-editor textarea { + height: 15em; +} + +.markdown-editor.fullscreen { + z-index: 9999; + width: 100%; + height: 100%; + position: fixed; + top: 0; + left: 0; +} + +.markdown-editor.fullscreen #markdown-preview { + height: 99%; +} + +.edit_legislation_draft_version .row { + margin-bottom: 2rem; +} + +.legislation-admin { + .menu .active > a { + background: none; + } +} + +.legislation-process-save { + @include breakpoint(medium) { + float: right; + } +} + +.legislation-question-delete { + @include breakpoint(medium) { + text-align: right; + } +} + +.legislation-process-index { + + .legislation-process-new { + @include breakpoint(medium) { + text-align: right; + } + } +} + +// 08. CMS // -------------- .cms_page_list { @@ -547,3 +617,289 @@ table { vertical-align: middle; } } + +.legislation-process-edit { + + .edit_legislation_process { + + small { + color: $text-medium; + } + + input[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) { + background: $white; + } + + .legislation-process-start, .legislation-process-end { + @include breakpoint(medium) { + line-height: 3rem; + } + } + + .legislation-process-end { + @include breakpoint(medium) { + text-align: right; + } + } + } +} + +.legislation-draft-versions-index { + + .legislation-process-question { + @include breakpoint(medium) { + text-align: right; + } + } + + table tr td { + padding: 0.25rem 0.375rem; + } +} + +.legislation-questions-form { + + input[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) { + background: $white; + margin-bottom: 0; + + @include breakpoint(medium) { + margin-bottom: 1rem; + } + } + + input::-webkit-input-placeholder { + font-style: italic; + } + + input:-moz-placeholder { /* Firefox 18- */ + font-style: italic; + } + + input::-moz-placeholder { /* Firefox 19+ */ + font-style: italic; + } + + input:-ms-input-placeholder { + font-style: italic; + } + + .legislation-questions-answers { + margin-bottom: 1rem; + } + + .field { + margin-bottom: 1rem; + + @include breakpoint(medium) { + margin-bottom: 0; + } + + a { + line-height: 3rem; + color: $delete; + + span { + text-decoration: underline; + } + + .icon-x { + vertical-align: sub; + text-decoration: none; + line-height: 0; + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } +} + +.legislation-draft-versions-form { + + .legislation-process-version { + @include breakpoint(medium) { + text-align: right; + } + } + + input[type]:not([type="submit"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]) { + background: $white; + } + + .control { + cursor: pointer; + margin-bottom: 1rem; + + small { + display: block; + margin-top: -1rem; + color: $text-medium; + + @include breakpoint(medium) { + margin-left: 0.25rem; + display: inline-block; + margin-top: 0; + } + } + } + + .fullscreen-container { + text-align: center; + background: #ccdbe6; + + .markdown-editor-header, + .markdown-editor-buttons { + display: none; + } + + a { + line-height: 8rem; + + span { + text-decoration: none; + font-size: $small-font-size; + } + + .icon-expand { + margin-left: 0.25rem; + vertical-align: sub; + text-decoration: none; + line-height: 0; + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } + + #legislation_draft_version_body { + font-family: $font-family-serif; + background: #f5f5f5; + height: 16em; + + &:focus { + border: 1px solid #cacaca; + box-shadow: inset 0 1px 2px rgba(34, 34, 34, 0.1); + } + } + + #markdown-preview { + font-family: $font-family-serif; + border: 1px solid #cacaca; + margin-bottom: 2rem; + + h1, h2, h3, h4, h5, h6 { + font-family: $font-family-serif !important; + font-size: 1rem; + line-height: 1.625rem; + margin-bottom: 0; + } + + p { + font-size: 1rem; + line-height: 1.625rem; + } + } + + .fullscreen { + + .markdown-area, + #markdown-preview { + display: block; + } + + .column { + padding: 0; + } + + .fullscreen-container { + text-align: left; + background: $admin-color; + padding: 0.5rem 1rem; + margin-bottom: 0; + + a { + line-height: 3rem; + + @include breakpoint(medium) { + float: right; + } + } + + .markdown-editor-header { + vertical-align: top; + display: inline-block; + color: $white; + + @include breakpoint(medium) { + line-height: 3rem; + } + } + + .truncate { + @include breakpoint(medium) { + width: 40vw; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .markdown-editor-buttons { + display: block; + + @include breakpoint(medium) { + display: inline-block; + float: right; + padding-left: 1rem; + } + + input { + font-size: $small-font-size; + padding: 0.5em 1em; + margin-left: 0; + margin-bottom: 0; + margin-top: 1rem; + + @include breakpoint(medium) { + margin: 0.5rem; + } + } + } + + a { + color: $white; + } + } + + #legislation_draft_version_body { + border-radius: 0; + padding: 1rem; + border: none; + + @include breakpoint(medium) { + padding: 1rem 2rem; + } + + &:focus { + border: none; + } + } + + #markdown-preview { + padding: 1rem; + border: none; + + @include breakpoint(medium) { + padding: 1rem 2rem; + } + } + } +} diff --git a/app/assets/stylesheets/annotator_overrides.scss b/app/assets/stylesheets/annotator_overrides.scss index 962cdefd7..5132d68eb 100644 --- a/app/assets/stylesheets/annotator_overrides.scss +++ b/app/assets/stylesheets/annotator_overrides.scss @@ -97,3 +97,28 @@ } } } + +.annotator-hl { + cursor: pointer; + background: rgba(255, 255, 10, 0.2); +} + +.annotator-hl.weight-1 { + background: #FFF9DA; +} +.annotator-hl.weight-2 { + background: #FFF5BC; +} +.annotator-hl.weight-3 { + background: #FFF1A2; +} +.annotator-hl.weight-4 { + background: #FFEF8C; +} +.annotator-hl.weight-5 { + background: #FFE95F; +} + +.current-annotation { + text-decoration: underline; +} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 0720c103f..c67d85471 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -7,6 +7,8 @@ @import "layout"; @import "participation"; @import "pages"; +@import "legislation"; +@import "legislation_process"; @import "custom"; @import "c3"; @import "annotator.min"; diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss index cde59e594..5805fb090 100644 --- a/app/assets/stylesheets/icons.scss +++ b/app/assets/stylesheets/icons.scss @@ -1,5 +1,4 @@ @charset "UTF-8"; - @font-face { font-family: 'icons'; src: font-url('icons.eot'); @@ -10,7 +9,6 @@ font-weight: normal; font-style: normal; } - [data-icon]:before { font-family: "icons" !important; content: attr(data-icon); @@ -23,7 +21,6 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - [class^="icon-"]:before, [class*=" icon-"]:before { font-family: "icons" !important; @@ -36,7 +33,6 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - .icon-angle-down:before { content: "\61"; } @@ -184,12 +180,18 @@ .icon-arrow-top:before { content: "\57"; } -.icon-help-1:before { - content: "\58"; -} .icon-checkmark-circle:before { content: "\59"; } +.icon-minus-square:before { + content: "\58"; +} +.icon-plus-square:before { + content: "\5a"; +} +.icon-expand:before { + content: "\30"; +} .icon-telegram:before { content: "\31"; } diff --git a/app/assets/stylesheets/legislation.scss b/app/assets/stylesheets/legislation.scss new file mode 100644 index 000000000..950f9ce23 --- /dev/null +++ b/app/assets/stylesheets/legislation.scss @@ -0,0 +1,131 @@ +// Table of Contents +// +// 01. Hero +// 02. Sidebar menu +// 03. Legislation cards +// + +// 01. Hero +// ----------------- +.brand-heading { + background: $brand; + margin-bottom: 5rem; + + .column { + padding-top: 10rem; + padding-bottom: 10rem; + + h4 { + font-weight: 400; + text-align: center; + color: white; + } + } +} + +// 02. Sidebar menu +// ----------------- +.legislation-categories { + + .menu.simple { + border-bottom: none; + list-style: none; + padding-left: 0; + margin-left: 0; + margin-top: 0; + + @include breakpoint(medium) { + margin: 1.5rem 0; + } + + li { + display: block; + cursor: pointer; + margin-bottom: 1rem; + + @include breakpoint(medium) { + margin-bottom: 2rem; + max-width: 80%; + } + } + + li.active { + font-weight: 700; + } + } +} + +// 03. Legislation cards +// ----------------- +.legislation { + margin: 0 0 5rem 0; + background: white; + border: 1px solid; + border-color: #e5e6e9 #dfe0e4 #d0d1d5; + border-radius: 0; + box-shadow: 0px 1px 3px 0 #DEE0E3; + min-height: 12rem; + padding: 2rem 0 0 0; +} + +.button-legislation { + background: white; + border: 1px solid #2C9BE5; + color: #2C9BE5; + display: inline-block; + font-weight: 700; + margin-top: rem-calc(12); + + .icon-comments { + margin-right: 0.5rem; + color: $text-medium; + transition: color 0.25s ease-out, color 0.25s ease-out; + } + + &:hover, &:active { + border: 1px solid lighten(#2C9BE5, 25%); + cursor: pointer; + } + + &:hover .icon-comments, &:active .icon-comments { + color: white; + } +} + +.legislation-text { + margin-bottom: 1rem; + + h3 a { + color: $black; + } +} + +.legislation-calendar-info p { + font-size: $small-font-size; + color: $text-medium; + margin-bottom: 0; +} + +.legislation-calendar { + background: #E5ECF2; + padding-top: 1rem; + + h5 { + margin-left: 0.25rem; + margin-bottom: 0; + color: #61686E; + + @include breakpoint(medium) { + margin-left: 0; + } + } + + p { + margin-left: 0.25rem; + font-size: $small-font-size; + + @include breakpoint(medium) { + margin-left: 0; + } + } +} diff --git a/app/assets/stylesheets/legislation_process.scss b/app/assets/stylesheets/legislation_process.scss new file mode 100644 index 000000000..fb2bdecad --- /dev/null +++ b/app/assets/stylesheets/legislation_process.scss @@ -0,0 +1,1176 @@ +// Table of Contents +// +// 01. Utils +// 02. Hero +// 03. Legislation process navigation +// 04. Debate list +// 05. Debate quiz +// 06. Legislation draft +// 07. Legislation allegations +// 08. Legislation changes +// 09. Legislation comments +// 10. Legislation draft comment +// + +// 01. Utils +// ----------------- +.grey { + color: #878787; +} + +.grey-heading { + background: #E6E6E6; +} + +.center { + text-align: center; +} + +.right { + text-align: right; +} + +.strong { + font-weight: 700; +} + +$epigraph-font-size: rem-calc(15); +$epigraph-line-height: rem-calc(22); + +// 02. Hero +// ----------------- +.legislation-hero { + padding-top: 1.5rem; + + @include breakpoint(medium) { + padding-top: 3.5rem; + } + + h4 { + color: #878787; + text-transform: uppercase; + font-weight: 400; + } + + ul { + list-style: none; + margin-left: 0; + + li:before { + vertical-align: text-bottom; + padding-right: 0.5rem; + content: "■"; + color: #8AA8BE; + } + } + + #debate-show { + display: none; + } + + .debate-add-info { + margin-top: 3rem; + padding-top: 4rem; + border-top: 1px solid darken($border, 10%); + + @include breakpoint(medium) { + margin-bottom: 2rem; + } + + .debate-info-wrapper { + + h2 { + font-size: $lead-font-size; + + @include breakpoint(medium) { + float: left; + } + } + + p { + font-size: $epigraph-font-size; + line-height: $epigraph-line-height; + + @include breakpoint(medium) { + margin-left: 25%; + } + } + } + } + + .half-gradient { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#e6e6e6+0,e6e6e6+50,ffffff+50 */ + background: #e6e6e6; /* Old browsers */ + background: -moz-linear-gradient(top, #e6e6e6 0%, #e6e6e6 50%, #ffffff 50%); /* FF3.6-15 */ + background: -webkit-linear-gradient(top, #e6e6e6 0%,#e6e6e6 50%,#ffffff 50%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to bottom, #e6e6e6 0%,#e6e6e6 50%,#ffffff 50%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e6e6e6', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */ + } + + .center .button { + background: $brand; + margin-bottom: 0; + } + + .headline { + margin-bottom: 1rem; + + @include breakpoint(medium) { + margin-bottom: 4rem; + + } + } + + .description { + margin-bottom: 1rem; + + p { + font-size: $epigraph-font-size; + line-height: $epigraph-line-height; + } + + ul { + font-size: $epigraph-font-size; + line-height: $epigraph-line-height; + + li { + margin-bottom: 1rem; + + p { + display: inline; + margin-bottom: 0; + } + } + } + + h4 { + font-size: $base-font-size; + } + } + + .button-subscribe { + margin-top: 1rem; + + h3 { + margin-bottom: 0; + } + + p { + margin-bottom: 0; + font-size: $small-font-size; + } + + &:hover h3 { + color: white; + } + + @include breakpoint(medium) { + padding: 0.5em 1em; + margin-top: 3rem; + } + } +} + +// 03. Legislation process navigation +// ----------------- +.legislation-process-categories { + position: relative; + + .legislation-process-list { + border-bottom: 1px solid $medium-gray; + margin: 0 1rem 1rem 1rem; + + @include breakpoint(medium) { + margin-left: 0; + } + + ul { + position: relative; + max-width: 75rem; + margin-left: auto; + margin-right: auto; + list-style: none; + padding-top: 4rem; + padding-left: 0; + margin-bottom: 0; + + @include breakpoint(medium) { + padding-left: 1rem; + } + + svg { + position: absolute; + top: 1.25rem; + + @include breakpoint(1280px) { + transform: rotate(-6deg); + left: -1rem; + } + } + + li { + cursor: pointer; + display: inline-block; + margin: 0 1rem 1rem 0; + transition: all 0.4s; + border-bottom: 2px solid transparent; + + &:first-of-type { + margin-left: 0; + } + + &:hover, + &:active, + &:focus { + border-bottom: 2px solid $brand; + } + + @media (min-width: 950px) { + margin: 0 0 0 3rem; + } + + a, + h4 { + display: block; + color: #6D6D6D; + margin-bottom: 0; + } + + a { + &:hover, &:active { + text-decoration: none; + } + + p { + margin-bottom: 0; + + @include breakpoint(medium) { + margin-bottom: 1rem; + } + } + } + } + + .active { + border-bottom: 2px solid $brand; + } + } + } +} + +// 04. Debate list +// ----------------- +.debate-chooser { + padding: 2rem 1rem; + + @include breakpoint(medium) { + .debate-chooser { + padding: 2rem 3rem; + } + } + + .debate-block { + margin-bottom: 2.5rem; + + .debate-type { + text-transform: uppercase; + color: #178DCC; + font-weight: 700; + font-size: $small-font-size; + + .icon-debates { + margin-left: 0.2rem; + } + } + + .debate-title a { + color: $brand; + } + + .debate-meta, + .debate-meta a { + font-size: $small-font-size; + color: #6D6D6D; + + .icon-comments { + margin-right: 0.2rem; + } + } + } + + .debate-info { + padding: 1rem; + background: #F4F4F4; + } +} + +// 05. Debate quiz +// ----------------- +.debate-questions { + .comments { + margin-top: 4rem; + } + + .quiz-header { + margin-bottom: 2rem; + + .quiz-title, .quiz-next { + padding: 1rem; + height: 6rem; + } + + .quiz-title { + background: #E5ECF2; + + .quiz-header-title { + margin-bottom: 0; + text-transform: uppercase; + color: #979B9F; + font-weight: 700; + font-size: $small-font-size; + } + } + + h4 a { + color: $brand; + } + + h4 a:hover { + text-decoration: none; + } + + .quiz-next-link { + display: block; + + &:hover, &:active { + text-decoration: none; + } + + .quiz-next { + background: #CCDBE5; + font-weight: 700; + color: $brand; + font-size: $small-font-size; + text-align: right; + text-transform: uppercase; + transition: background 0.25s ease-out, background 0.25s ease-out; + + .icon-angle-right { + vertical-align: sub; + } + + &:hover, &:active { + text-decoration: none; + background: $brand; + color: white; + + .icon-angle-right { + color: white; + } + } + } + } + } + + .quiz-question { + margin-bottom: 2rem; + } + + .debate-questions { + position: relative; + list-style: none; + + .participation-not-allowed { + padding-bottom: 3rem; + } + + .control { + position: relative; + display: inline-block; + color: #555; + cursor: pointer; + background: #fff; + border: 1px solid $border; + border-radius: 4px; + padding: 0.75rem 2.5rem; + margin-right: 1.5rem; + margin-bottom: 0.5rem; + } + + .active { + background: #CCDBE6; + border: none; + } + + .control input { + position: absolute; + opacity: 0; + z-index: -1; + } + + .control input:checked ~ .control-indicator { + background-color: $brand; + border: none; + } + + .radio .control-indicator { + border-radius: 50%; + } + + .control-indicator { + position: absolute; + top: 0.95rem; + left: 0.95rem; + display: block; + width: 1rem; + height: 1rem; + line-height: 1rem; + font-size: 65%; + text-align: center; + border: 2px solid #9C9C9C; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + } +} + +// 06. Legislation draft +// ----------------- +.debate-draft { + padding: 10rem 2rem 15rem 2rem; + display: block; + background: #F2F2F2; + + button { + height: 90px; + + h3 { + margin-bottom: 0; + } + + p { + margin-bottom: 0; + font-size: $small-font-size; + } + } +} + +// 07. Legislation allegations +// ----------------- +.legislation-allegation { + padding-top: 1rem; + + #debate-show { + margin-top: 2rem; + } + + .headline { + margin-bottom: 0; + + .headline-small { + padding-top: 0.75rem; + } + } + + .button-circle { + line-height: 0; + padding: 0em; + width: 30px; + height: 30px; + border-radius: 50%; + + span { + padding-left: 1px; + + &:before { + line-height: 1.55; + } + } + } + + .icon-checkmark-circle { + font-size: 1.5rem; + vertical-align: bottom; + color: $text-medium; + margin-right: 0.5rem; + } + + .button-subscribed { + margin-top: 1rem; + border: 1px solid #D1D1D1; + background: #F2F2F2; + + h3 { + display: inline-block; + color: $text; + margin-bottom: 0; + } + + p { + margin-bottom: 0; + font-size: $small-font-size; + } + + &:hover h3 { + color: $text; + } + + @include breakpoint(medium) { + padding: 0.5em 1em; + } + } +} + +.draft-panels { + position: relative; + padding: 2rem 0; + + .draft-chooser { + margin-bottom: 2rem; + + h3 { + vertical-align: top; + display: inline-block; + font-weight: 400; + margin-right: 0.5rem; + } + + span { + margin-left: 0.25rem; + font-style: italic; + font-size: $small-font-size; + color: $text-medium; + vertical-align: top; + line-height: 2.35rem; + } + + .select-box { + position: relative; + + @include breakpoint(medium) { + display: inline-block; + } + + select { + margin-bottom: 0; + display: block; + } + + span { + vertical-align: inherit; + font-style: normal; + + a { + text-decoration: underline; + color: $text-medium + } + } + } + + .button { + margin-top: 1rem; + + @include breakpoint(medium) { + margin-top: 0; + } + } + } + + .draft-allegation { + @include breakpoint(medium) { + display: flex; + padding-left: 0.9375rem; + padding-right: 0.9375rem; + } + + .calc-index { + .draft-panel { + cursor: pointer; + } + + .draft-index-rotated { + display: none; + } + } + + // Panel calcs for desktop + @media screen and (min-width: 40em) { + .calc-index { + width: calc(35% - 25px); + } + + .calc-text { + width: calc(65% - 25px) + } + + .calc-comments { + cursor: pointer; + background: #F2F2F2; + width: 50px; + + .draft-panel { + .panel-title { + display: none; + } + } + } + } + + .border-right { + @include breakpoint(medium) { + border-right: 1px solid $border; + } + } + + .border-left { + @include breakpoint(medium) { + border-left: 1px solid $border; + } + } + + .draft-panel { + text-transform: uppercase; + font-weight: 700; + padding: 0.5rem 1rem; + color: #696969; + background: #F2F2F2; + font-size: $small-font-size; + + .icon-comments { + margin-right: 0.25rem; + } + + .icon-banner { + line-height: 0; + color: $text-medium; + vertical-align: sub; + margin-right: 0.25rem; + } + } + + .draft-index { + ul:first-child { + font-size: 1rem; + text-decoration: underline; + margin-left: 2.25rem; + margin-top: 1.5rem; + } + + ul { + list-style: none; + + li { + margin-bottom: 1rem; + } + .open::before { + cursor: pointer; + position: absolute; + margin-left: -1.25rem; + font-family: "icons"; + content: "\58"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + .closed::before { + cursor: pointer; + position: absolute; + margin-left: -1.25rem; + font-family: "icons"; + content: "\5a"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + } + } + + .draft-text { + position: relative; + padding: 1rem; + + @include breakpoint(medium) { + padding: 2rem 2rem 2rem 3rem; + }; + + h2 { + font-weight: 400; + margin-bottom: 2rem; + margin-top: 2rem; + } + + h3 { + font-weight: 400; + margin-bottom: 1rem; + } + + .anchor::before { + display: none; + content: "#"; + color: $text-medium; + position: absolute; + left: 0; + + @include breakpoint(medium) { + left: 1.5rem; + } + } + + a { + color: $text; + + &:hover, + &:active, + &:focus { + text-decoration: none; + + h3 { + color: $text; + } + + .anchor::before { + display: block; + } + } + } + } + + .calc-comments { + position: relative; + + .comment-box { + display: none; + } + + .draft-comments-rotated { + @include breakpoint(medium) { + font-size: $small-font-size; + text-transform: uppercase; + font-weight: 700; + color: #696969; + margin-top: 4rem; + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + } + } + + } + } + + .comments-on { + .calc-index { + width: 50px; + background: #F2F2F2; + cursor: pointer; + + .panel-title { + display: none; + } + + .draft-index { + display: none; + } + + .draft-index-rotated { + @include breakpoint(medium) { + display: block; + font-size: $small-font-size; + text-transform: uppercase; + font-weight: 700; + color: #696969; + margin-top: 1rem; + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + + .panel-title { + display: block; + } + } + } + } + + .calc-text { + width: calc(65% - 25px); + border-right: none; + + .show-comments { + width: 105%; + background: #FAFAFA; + padding: 0.25rem 2.5rem 0.25rem 0.25rem; + border: 1px solid $border; + margin-bottom: 1rem; + + p { + margin-bottom: 0; + } + } + + } + + .calc-comments { + background: white; + cursor: auto; + width: calc(35% - 25px); + + .draft-panel { + cursor: pointer; + } + + .draft-comments-rotated { + display: none; + } + + #comments-box { + position: absolute; + top: 230px; + } + + .comment-box { + width: 375px; + padding: 1rem; + background: #F9F9F9; + border: 1px solid $border; + display: block; + margin-bottom: 2rem; + + .button { + font-size: $small-font-size; + padding: 0.5em 1em; + } + + .publish-comment { + float: right; + } + + .comment-header { + color: #838383; + padding-bottom: 0.5rem; + margin-bottom: 1rem; + border-bottom: 1px solid $border; + + .comment-number { + color: $text; + display: inline-block; + } + + .icon-comment { + margin-right: 0.5rem; + } + + a .icon-expand { + color: #838383; + font-size: $small-font-size; + float: right; + } + } + + .comments-wrapper { + position: relative; + + .participation-not-allowed { + padding: 1.25rem 0.5rem; + } + } + + .comment-footer { + position: relative; + + .participation-not-allowed { + font-size: 0.875rem; + height: 50px; + padding: .85rem 0.75rem; + top: -18px; + } + } + + .comment-input { + padding-bottom: 4rem; + margin-bottom: 1rem; + margin-top: 1rem; + border-bottom: 1px solid $border; + + .comment-advice { + border-top: 1px solid #D0D0D0; + border-right: 1px solid #D0D0D0; + border-left: 1px solid #D0D0D0; + width: 100%; + padding: 0.5rem; + display: inline-block; + font-size: $small-font-size; + background: #DFDFDF; + + .icon-proposals { + color: #838383; + } + + a { + color: #87A3B9; + text-decoration: underline; + } + } + + textarea { + border-radius: 0; + box-shadow: none; + border-bottom: 1px solid #D0D0D0; + border-right: 1px solid #D0D0D0; + border-left: 1px solid #D0D0D0; + width: 100%; + height: 200px; + margin-bottom: 0.5rem; + } + + .comment-actions { + .cancel-comment { + color: #87A3B9; + text-decoration: underline; + font-size: $small-font-size; + display: inline-block; + } + } + } + + .comment { + border-bottom: 1px solid $border; + padding-bottom: 0.75rem; + margin-bottom: 1rem; + font-size: $small-font-size; + + .comment-text { + margin-bottom: 0.5rem; + + p { + line-height: 1.5; + font-size: $small-font-size; + + &:last-child { + margin-bottom: 0.5rem; + } + } + } + + .comment-meta { + + .comment-more-info { + display: inline-block; + + .comment-expand { + display: inline-block; + + &::after { + content: "|"; + color: #838383; + } + } + .comment-replies { + display: inline-block; + } + } + + .comment-votes { + color: #838383; + float: right; + display: inline-block; + + .comment-votes-number { + margin-right: 0.25rem; + display: inline-block; + + &::after { + margin-left: 0.25rem; + content: "|"; + } + } + + .icon-like, + .icon-unlike { + cursor: pointer; + color: #C7C7C7; + + &:hover, + &:active, + &:focus { + color: #838383; + } + } + + .icon-like { + margin-right: 0.25rem; + } + } + } + } + } + + .draft-panel { + background: #E5E5E5; + border-left: 1px solid #D4D4D4; + + .panel-title { + display: inline-block; + } + } + + .show-for-medium { + .panel-title { + display: none; + } + } + } + } +} + +// 08. Legislation changes +// ----------------- +.legislation-changes { + ul { + list-style: none; + margin-left: 0; + + li { + margin-bottom: 1rem; + + &::before { + margin-right: 0.25rem; + content: "—" + } + + .changes-link { + display: block; + margin-left: 1rem; + font-size: $small-font-size; + + @include breakpoint(medium) { + display: inline-block; + } + + a { + span { + text-decoration: underline; + } + + .icon-external { + text-decoration: none; + color: #999999; + line-height: 0; + vertical-align: sub; + margin-left: 0.5rem; + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } + } + } +} + +// 09. Legislation comments +// ----------------- +.legislation-comments { + + .pull-right { + float: right; + } + + .comment-section { + background: #FAFAFA; + padding: 1rem; + border: 1px solid #DEE0E3; + margin-top: 0.25rem; + margin-bottom: 1rem; + } + + .comment { + margin-bottom: 3rem; + + a { + span { + text-decoration: underline; + } + + .icon-expand, + .icon-comments { + text-decoration: none; + color: #999999; + line-height: 0; + } + + .icon-expand { + margin-left: 0.25rem; + } + + .icon-comments { + margin-right: 0.25rem; + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } +} + +// 10. Legislation draft comment +// ----------------- +.legislation-comment { + + .annotation-share-comment { + margin-bottom: 2rem; + } + + .pull-right { + float: right; + } + + .comment-section { + background: #FAFAFA; + padding: 1rem; + border: 1px solid #DEE0E3; + margin-top: 0.25rem; + margin-bottom: 1rem; + } + + .comment { + margin-bottom: 3rem; + + a { + span { + text-decoration: underline; + } + + .icon-expand, + .icon-comments { + text-decoration: none; + color: #999999; + line-height: 0; + } + + .icon-expand { + margin-left: 0.25rem; + } + + .icon-comments { + margin-right: 0.25rem; + } + + &:active, + &:focus, + &:hover { + text-decoration: none; + } + } + } +} diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 0c3ff069a..5658a4e6b 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -299,7 +299,10 @@ .proposal-show, .investment-project-show, .budget-investment-show, -.polls-show { +.polls-show, +.debate-quiz, +.budget-investment-show, +.draft-panels { p { word-wrap: break-word; @@ -493,7 +496,7 @@ } } -.debate, .proposal, .investment-project, .budget-investment { +.debate, .proposal, .investment-project, .budget-investment, .legislation { margin: $line-height/4 0; .panel { diff --git a/app/controllers/admin/legislation/base_controller.rb b/app/controllers/admin/legislation/base_controller.rb new file mode 100644 index 000000000..b6c62f426 --- /dev/null +++ b/app/controllers/admin/legislation/base_controller.rb @@ -0,0 +1,14 @@ +class Admin::Legislation::BaseController < Admin::BaseController + include FeatureFlags + + feature_flag :legislation + + helper_method :namespace + + private + + def namespace + "admin" + end + +end diff --git a/app/controllers/admin/legislation/draft_versions_controller.rb b/app/controllers/admin/legislation/draft_versions_controller.rb new file mode 100644 index 000000000..0d84addbf --- /dev/null +++ b/app/controllers/admin/legislation/draft_versions_controller.rb @@ -0,0 +1,44 @@ +class Admin::Legislation::DraftVersionsController < Admin::Legislation::BaseController + load_and_authorize_resource :process, class: "Legislation::Process" + load_and_authorize_resource :draft_version, class: "Legislation::DraftVersion", through: :process + + def index + @draft_versions = @process.draft_versions + end + + def create + if @draft_version.save + redirect_to admin_legislation_process_draft_versions_path, notice: t('admin.legislation.draft_versions.create.notice', link: legislation_process_draft_version_path(@process, @draft_version).html_safe) + else + flash.now[:error] = t('admin.legislation.draft_versions.create.error') + render :new + end + end + + def update + if @draft_version.update(draft_version_params) + redirect_to edit_admin_legislation_process_draft_version_path(@process, @draft_version), notice: t('admin.legislation.draft_versions.update.notice', link: legislation_process_draft_version_path(@process, @draft_version).html_safe) + else + flash.now[:error] = t('admin.legislation.draft_versions.update.error') + render :edit + end + end + + def destroy + @draft_version.destroy + redirect_to admin_legislation_process_draft_versions_path, notice: t('admin.legislation.draft_versions.destroy.notice') + end + + private + + def draft_version_params + params.require(:legislation_draft_version).permit( + :title, + :changelog, + :status, + :final_version, + :body, + :body_html + ) + end +end diff --git a/app/controllers/admin/legislation/processes_controller.rb b/app/controllers/admin/legislation/processes_controller.rb new file mode 100644 index 000000000..8cc678151 --- /dev/null +++ b/app/controllers/admin/legislation/processes_controller.rb @@ -0,0 +1,54 @@ +class Admin::Legislation::ProcessesController < Admin::Legislation::BaseController + has_filters %w{open next past all}, only: :index + + load_and_authorize_resource :process, class: "Legislation::Process" + + def index + @processes = ::Legislation::Process.send(@current_filter).order('id DESC').page(params[:page]) + end + + def create + if @process.save + redirect_to edit_admin_legislation_process_path(@process), notice: t('admin.legislation.processes.create.notice', link: legislation_process_path(@process).html_safe) + else + flash.now[:error] = t('admin.legislation.processes.create.error') + render :new + end + end + + def update + if @process.update(process_params) + redirect_to edit_admin_legislation_process_path(@process), notice: t('admin.legislation.processes.update.notice', link: legislation_process_path(@process).html_safe) + else + flash.now[:error] = t('admin.legislation.processes.update.error') + render :edit + end + end + + def destroy + @process.destroy + redirect_to admin_legislation_processes_path, notice: t('admin.legislation.processes.destroy.notice') + end + + private + + def process_params + params.require(:legislation_process).permit( + :title, + :description_summary, + :target_summary, + :description, + :target, + :how_to_participate, + :additional_info, + :start_date, + :end_date, + :debate_start_date, + :debate_end_date, + :draft_publication_date, + :allegations_start_date, + :allegations_end_date, + :final_publication_date + ) + end +end diff --git a/app/controllers/admin/legislation/questions_controller.rb b/app/controllers/admin/legislation/questions_controller.rb new file mode 100644 index 000000000..c8335e6df --- /dev/null +++ b/app/controllers/admin/legislation/questions_controller.rb @@ -0,0 +1,45 @@ +class Admin::Legislation::QuestionsController < Admin::Legislation::BaseController + load_and_authorize_resource :process, class: "Legislation::Process" + load_and_authorize_resource :question, class: "Legislation::Question", through: :process + + def index + @questions = @process.questions + end + + def new + @question.question_options.build + end + + def create + @question.author = current_user + if @question.save + redirect_to admin_legislation_process_questions_path, notice: t('admin.legislation.questions.create.notice', link: legislation_process_question_path(@process, @question).html_safe) + else + flash.now[:error] = t('admin.legislation.questions.create.error') + render :new + end + end + + def update + if @question.update(question_params) + redirect_to edit_admin_legislation_process_question_path(@process, @question), notice: t('admin.legislation.questions.update.notice', link: legislation_process_question_path(@process, @question).html_safe) + else + flash.now[:error] = t('admin.legislation.questions.update.error') + render :edit + end + end + + def destroy + @question.destroy + redirect_to admin_legislation_process_questions_path, notice: t('admin.legislation.questions.destroy.notice') + end + + private + + def question_params + params.require(:legislation_question).permit( + :title, + question_options_attributes: [:id, :value, :_destroy] + ) + end +end diff --git a/app/controllers/annotations_controller.rb b/app/controllers/annotations_controller.rb index d70a2e9be..8b004076c 100644 --- a/app/controllers/annotations_controller.rb +++ b/app/controllers/annotations_controller.rb @@ -24,7 +24,7 @@ class AnnotationsController < ApplicationController end def search - @annotations = Annotation.where(legislation_id: params[:legislation_id]) + @annotations = Annotation.where(legacy_legislation_id: params[:legacy_legislation_id]) annotations_hash = { total: @annotations.size, rows: @annotations } render json: annotations_hash.to_json(methods: :permissions) end @@ -35,6 +35,6 @@ class AnnotationsController < ApplicationController params .require(:annotation) .permit(:quote, :text, ranges: [:start, :startOffset, :end, :endOffset]) - .merge(legislation_id: params[:legislation_id]) + .merge(legacy_legislation_id: params[:legacy_legislation_id]) end end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 0a0018c9f..fb5e481a0 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,6 +1,10 @@ class CommentsController < ApplicationController + include CustomUrlsHelper + before_action :authenticate_user!, only: :create before_action :load_commentable, only: :create + before_action :verify_resident_for_commentable!, only: :create + before_action :verify_comments_open!, only: [:create, :vote] before_action :build_comment, only: :create load_and_authorize_resource @@ -75,6 +79,22 @@ class CommentsController < ApplicationController notifiable = comment.commentable end Notification.add(notifiable.author_id, notifiable) unless comment.author_id == notifiable.author_id - end + end -end \ No newline at end of file + def verify_resident_for_commentable! + return if current_user.administrator? || current_user.moderator? + + if @commentable.respond_to?(:comments_for_verified_residents_only?) && @commentable.comments_for_verified_residents_only? + verify_resident! + end + end + + def verify_comments_open! + return if current_user.administrator? || current_user.moderator? + + if @commentable.respond_to?(:comments_closed?) && @commentable.comments_closed? + redirect_to @commentable, alert: t('comments.comments_closed') + end + end + +end diff --git a/app/controllers/legacy_legislations_controller.rb b/app/controllers/legacy_legislations_controller.rb new file mode 100644 index 000000000..103c81cea --- /dev/null +++ b/app/controllers/legacy_legislations_controller.rb @@ -0,0 +1,8 @@ +class LegacyLegislationsController < ApplicationController + load_and_authorize_resource + + def show + @legacy_legislation = LegacyLegislation.find(params[:id]) + end + +end diff --git a/app/controllers/legislation/annotations_controller.rb b/app/controllers/legislation/annotations_controller.rb new file mode 100644 index 000000000..93de149fa --- /dev/null +++ b/app/controllers/legislation/annotations_controller.rb @@ -0,0 +1,101 @@ +class Legislation::AnnotationsController < ApplicationController + skip_before_action :verify_authenticity_token + + before_action :authenticate_user!, only: [:create, :new_comment] + before_action :convert_ranges_parameters, only: [:create] + + load_and_authorize_resource :process + load_and_authorize_resource :draft_version, through: :process + load_and_authorize_resource + + has_orders %w{most_voted newest oldest}, only: :show + + def index + @annotations = @draft_version.annotations + end + + def show + @commentable = @annotation + @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + set_comment_flags(@comment_tree.comments) + end + + def create + if !@process.open_phase?(:allegations) || @draft_version.final_version? + render json: {}, status: :not_found and return + end + + existing_annotation = @draft_version.annotations.where( + range_start: annotation_params[:ranges].first[:start], range_start_offset: annotation_params[:ranges].first[:startOffset].to_i, + range_end: annotation_params[:ranges].first[:end], range_end_offset: annotation_params[:ranges].first[:endOffset].to_i).first + + if @annotation = existing_annotation + if comment = @annotation.comments.create(body: annotation_params[:text], user: current_user) + render json: @annotation.to_json + else + render json: comment.errors.full_messages, status: :unprocessable_entity + end + else + @annotation = @draft_version.annotations.new(annotation_params) + @annotation.author = current_user + if @annotation.save + track_event + render json: @annotation.to_json + else + render json: @annotation.errors.full_messages, status: :unprocessable_entity + end + end + end + + def search + @annotations = @draft_version.annotations.order("LENGTH(quote) DESC") + annotations_hash = { total: @annotations.size, rows: @annotations } + render json: annotations_hash.to_json(methods: :weight) + end + + def comments + @annotation = Legislation::Annotation.find(params[:annotation_id]) + @comment = @annotation.comments.new + end + + def new + respond_to do |format| + format.js + end + end + + def new_comment + @draft_version = Legislation::DraftVersion.find(params[:draft_version_id]) + @annotation = @draft_version.annotations.find(params[:annotation_id]) + @comment = @annotation.comments.new(body: params[:comment][:body], user: current_user) + if @comment.save + @comment = @annotation.comments.new + end + + respond_to do |format| + format.js { render :new_comment } + end + end + + private + + def annotation_params + params + .require(:legislation_annotation) + .permit(:quote, :text, ranges: [:start, :startOffset, :end, :endOffset]) + end + + def track_event + ahoy.track "legislation_annotation_created".to_sym, + "legislation_annotation_id": @annotation.id, + "legislation_draft_version_id": @draft_version.id + end + + def convert_ranges_parameters + if params[:legislation_annotation] && params[:legislation_annotation][:ranges] && params[:legislation_annotation][:ranges].is_a?(String) + params[:legislation_annotation][:ranges] = JSON.parse(params[:legislation_annotation][:ranges]) + end + rescue JSON::ParserError + end + +end diff --git a/app/controllers/legislation/answers_controller.rb b/app/controllers/legislation/answers_controller.rb new file mode 100644 index 000000000..372398e41 --- /dev/null +++ b/app/controllers/legislation/answers_controller.rb @@ -0,0 +1,41 @@ +class Legislation::AnswersController < Legislation::BaseController + before_action :authenticate_user! + before_action :verify_resident! + + load_and_authorize_resource :process + load_and_authorize_resource :question, through: :process + load_and_authorize_resource :answer, through: :question + + respond_to :html, :js + + def create + if @process.open_phase?(:debate) + @answer.user = current_user + @answer.save + track_event + respond_to do |format| + format.js + format.html { redirect_to legislation_process_question_path(@process, @question) } + end + else + respond_to do |format| + format.js { render json: {} , status: :not_found } + format.html { redirect_to legislation_process_question_path(@process, @question), alert: t('legislation.questions.participation.phase_not_open') } + end + end + end + + private + def answer_params + params.require(:legislation_answer).permit( + :legislation_question_option_id, + ) + end + + def track_event + ahoy.track "legislation_answer_created".to_sym, + "legislation_answer_id": @answer.id, + "legislation_question_option_id": @answer.legislation_question_option_id, + "legislation_question_id": @answer.legislation_question_id + end +end diff --git a/app/controllers/legislation/base_controller.rb b/app/controllers/legislation/base_controller.rb new file mode 100644 index 000000000..ca609ecba --- /dev/null +++ b/app/controllers/legislation/base_controller.rb @@ -0,0 +1,5 @@ +class Legislation::BaseController < ApplicationController + include FeatureFlags + + feature_flag :legislation +end diff --git a/app/controllers/legislation/draft_versions_controller.rb b/app/controllers/legislation/draft_versions_controller.rb new file mode 100644 index 000000000..cdac2c9d5 --- /dev/null +++ b/app/controllers/legislation/draft_versions_controller.rb @@ -0,0 +1,40 @@ +class Legislation::DraftVersionsController < Legislation::BaseController + load_and_authorize_resource :process + load_and_authorize_resource :draft_version, through: :process + + def index + end + + def show + @draft_versions_list = visible_draft_versions + @draft_version = @draft_versions_list.find(params[:id]) + end + + def changes + @draft_versions_list = visible_draft_versions + @draft_version = @draft_versions_list.find(params[:draft_version_id]) + end + + def go_to_version + version = visible_draft_versions.find(params[:draft_version_id]) + + if params[:redirect_action] == 'changes' + redirect_to legislation_process_draft_version_changes_path(@process, version) + elsif params[:redirect_action] == 'annotations' + redirect_to legislation_process_draft_version_annotations_path(@process, version) + else + redirect_to legislation_process_draft_version_path(@process, version) + end + end + + private + + def visible_draft_versions + if current_user && current_user.administrator? + @process.draft_versions + else + @process.draft_versions.published + end + end + +end diff --git a/app/controllers/legislation/processes_controller.rb b/app/controllers/legislation/processes_controller.rb new file mode 100644 index 000000000..7aff709d0 --- /dev/null +++ b/app/controllers/legislation/processes_controller.rb @@ -0,0 +1,78 @@ +class Legislation::ProcessesController < Legislation::BaseController + has_filters %w{open next past}, only: :index + load_and_authorize_resource + + def index + @current_filter ||= 'open' + @processes = ::Legislation::Process.send(@current_filter).page(params[:page]) + end + + def show + if @process.active_phase?(:allegations) && @process.show_phase?(:allegations) && draft_version = @process.draft_versions.published.last + redirect_to legislation_process_draft_version_path(@process, draft_version) + elsif @process.active_phase?(:debate) + redirect_to legislation_process_debate_path(@process) + else + redirect_to legislation_process_allegations_path(@process) + end + end + + def debate + phase :debate + + if @process.show_phase?(:debate) + render :debate + else + render :phase_not_open + end + end + + def draft_publication + phase :draft_publication + + if @process.show_phase?(@phase) + if draft_version = @process.draft_versions.published.last + redirect_to legislation_process_draft_version_path(@process, draft_version) + else + render :phase_empty + end + else + render :phase_not_open + end + end + + def allegations + phase :allegations + + if @process.show_phase?(@phase) + if draft_version = @process.draft_versions.published.last + redirect_to legislation_process_draft_version_path(@process, draft_version) + else + render :phase_empty + end + else + render :phase_not_open + end + end + + def final_version_publication + phase :final_version_publication + + if @process.show_phase?(@phase) + if final_version = @process.final_draft_version + redirect_to legislation_process_draft_version_path(@process, final_version) + else + render :phase_empty + end + else + render :phase_not_open + end + end + + private + + def phase(phase) + @process = ::Legislation::Process.find(params[:process_id]) + @phase = phase + end +end diff --git a/app/controllers/legislation/questions_controller.rb b/app/controllers/legislation/questions_controller.rb new file mode 100644 index 000000000..d50443281 --- /dev/null +++ b/app/controllers/legislation/questions_controller.rb @@ -0,0 +1,13 @@ +class Legislation::QuestionsController < Legislation::BaseController + load_and_authorize_resource :process + load_and_authorize_resource :question, through: :process + + has_orders %w{most_voted newest oldest}, only: :show + + def show + @commentable = @question + @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + set_comment_flags(@comment_tree.comments) + @answer = @question.answer_for_user(current_user) || Legislation::Answer.new + end +end diff --git a/app/controllers/legislations_controller.rb b/app/controllers/legislations_controller.rb deleted file mode 100644 index a95f95ef2..000000000 --- a/app/controllers/legislations_controller.rb +++ /dev/null @@ -1,8 +0,0 @@ -class LegislationsController < ApplicationController - load_and_authorize_resource - - def show - @legislation = Legislation.find(params[:id]) - end - -end \ No newline at end of file diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index ab346e802..99f401f7b 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -1,4 +1,6 @@ class NotificationsController < ApplicationController + include CustomUrlsHelper + before_action :authenticate_user! after_action :mark_as_read, only: :show skip_authorization_check diff --git a/app/controllers/sandbox_controller.rb b/app/controllers/sandbox_controller.rb new file mode 100644 index 000000000..12b5dd91e --- /dev/null +++ b/app/controllers/sandbox_controller.rb @@ -0,0 +1,45 @@ +class SandboxController < ApplicationController + skip_authorization_check + + layout :set_layout + + helper_method(:namespace) + + def index + @templates = Dir.glob(Rails.root.join('app/views/sandbox/*.html.erb').to_s).map do |filename| + filename = File.basename(filename, File.extname(filename)) + filename unless filename.starts_with?('_') || filename == 'index.html' + end.compact + end + + def show + if params[:template].index('.') # CVE-2014-0130 + render :action => "index" + elsif lookup_context.exists?("sandbox/#{params[:template]}") + if params[:template] == "index" + render :action => "index" + else + render "sandbox/#{params[:template]}" + end + + elsif lookup_context.exists?("sandbox/#{params[:template]}/index") + render "sandbox/#{params[:template]}/index" + else + render :action => "index" + end + end + + private + + def set_layout + if params[:template] && params[:template].split("_").first == "admin" + "admin" + else + "application" + end + end + + def namespace + "admin" + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6a040ec69..3cd7580ee 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -18,6 +18,8 @@ module ApplicationHelper end def markdown(text) + return text if text.blank? + # See https://github.com/vmg/redcarpet for options render_options = { filter_html: false, diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index df6e57b61..3f760f73c 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -1,11 +1,31 @@ module CommentsHelper + def comment_tree_title_text(commentable) + if commentable.class == Legislation::Question + t("legislation.questions.comments.comments_title") + else + t("comments_helper.comments_title") + end + end + + def leave_comment_text(commentable) + if commentable.class == Legislation::Question + t("legislation.questions.comments.form.leave_comment") + else + t("comments.form.leave_comment") + end + end + def comment_link_text(parent_id) parent_id.present? ? t("comments_helper.reply_link") : t("comments_helper.comment_link") end - def comment_button_text(parent_id) - parent_id.present? ? t("comments_helper.reply_button") : t("comments_helper.comment_button") + def comment_button_text(parent_id, commentable) + if commentable.class == Legislation::Question + parent_id.present? ? t("comments_helper.reply_button") : t("legislation.questions.comments.comment_button") + else + parent_id.present? ? t("comments_helper.reply_button") : t("comments_helper.comment_button") + end end def parent_or_commentable_dom_id(parent_id, commentable) @@ -21,12 +41,17 @@ module CommentsHelper end def commentable_path(comment) - if comment.commentable_type == "Budget::Investment" - budget_investment_path(comment.commentable.budget_id, comment.commentable) - elsif comment.commentable_type == "Poll::Question" - question_path(comment.commentable) + commentable = comment.commentable + + case comment.commentable_type + when "Budget::Investment" + budget_investment_path(commentable.budget_id, commentable) + when "Legislation::Question" + legislation_process_question_path(commentable.process, commentable) + when "Legislation::Annotation" + legislation_process_draft_version_annotation_path(commentable.draft_version.process, commentable.draft_version, commentable) else - comment.commentable + commentable end end @@ -50,4 +75,22 @@ module CommentsHelper end end -end \ No newline at end of file + def require_verified_resident_for_commentable?(commentable, current_user) + return false if current_user.administrator? || current_user.moderator? + + commentable.respond_to?(:comments_for_verified_residents_only?) && commentable.comments_for_verified_residents_only? && !current_user.residence_verified? + end + + def comments_closed_for_commentable?(commentable) + commentable.respond_to?(:comments_closed?) && commentable.comments_closed? + end + + def comments_closed_text(commentable) + if commentable.class == Legislation::Question + t("legislation.questions.comments.comments_closed") + else + t("comments.comments_closed") + end + end + +end diff --git a/app/helpers/custom_urls_helper.rb b/app/helpers/custom_urls_helper.rb new file mode 100644 index 000000000..1519e771e --- /dev/null +++ b/app/helpers/custom_urls_helper.rb @@ -0,0 +1,9 @@ +module CustomUrlsHelper + def legislation_question_url(question) + legislation_process_question_url(question.process, question) + end + + def legislation_annotation_url(annotation) + legislation_process_question_url(annotation.draft_version.process, annotation.draft_version, annotation) + end +end diff --git a/app/helpers/legislation_helper.rb b/app/helpers/legislation_helper.rb new file mode 100644 index 000000000..3a993d683 --- /dev/null +++ b/app/helpers/legislation_helper.rb @@ -0,0 +1,9 @@ +module LegislationHelper + def format_date(date) + l(date, format: "%d %b %Y") if date + end + + def format_date_for_calendar_form(date) + l(date, format: "%d/%m/%Y") if date + end +end diff --git a/app/helpers/text_helper.rb b/app/helpers/text_helper.rb new file mode 100644 index 000000000..dda3ba721 --- /dev/null +++ b/app/helpers/text_helper.rb @@ -0,0 +1,9 @@ +module TextHelper + def first_paragraph(text) + if text.blank? + "" + else + text.strip.split("\n").first + end + end +end diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 8df52e6eb..f09333ff0 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -32,7 +32,7 @@ module Abilities can :mark_featured, Debate can :unmark_featured, Debate - can :comment_as_administrator, [Debate, Comment, Proposal, Poll::Question, Budget::Investment] + can :comment_as_administrator, [Debate, Comment, Proposal, Poll::Question, Budget::Investment, Legislation::Question, Legislation::Annotation] can [:search, :create, :index, :destroy], ::Moderator can [:search, :create, :index, :summary], ::Valuator @@ -64,6 +64,12 @@ module Abilities can :manage, SiteCustomization::Page can :manage, SiteCustomization::Image can :manage, SiteCustomization::ContentBlock + + can [:manage], ::Legislation::Process + can [:manage], ::Legislation::DraftVersion + can [:manage], ::Legislation::Question + cannot :comment_as_moderator, [::Legislation::Question, Legislation::Annotation] + end end end diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index c2b6cd6d7..50949671f 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -11,13 +11,18 @@ module Abilities can [:read, :welcome], Budget can :read, Budget::Investment can :read, SpendingProposal - can :read, Legislation + can :read, LegacyLegislation can :read, User can [:search, :read], Annotation can [:read], Budget can [:read], Budget::Group can [:read, :print], Budget::Investment can :new, DirectMessage + can [:read, :debate, :draft_publication, :allegations, :final_version_publication], Legislation::Process + can [:read, :changes, :go_to_version], Legislation::DraftVersion + can [:read], Legislation::Question + can [:create], Legislation::Answer + can [:search, :comments, :read, :create, :new_comment], Legislation::Annotation end end end diff --git a/app/models/abilities/moderator.rb b/app/models/abilities/moderator.rb index 5740e302e..ba8c659ba 100644 --- a/app/models/abilities/moderator.rb +++ b/app/models/abilities/moderator.rb @@ -5,7 +5,7 @@ module Abilities def initialize(user) self.merge Abilities::Moderation.new(user) - can :comment_as_moderator, [Debate, Comment, Proposal, Budget::Investment, Poll::Question] + can :comment_as_moderator, [Debate, Comment, Proposal, Budget::Investment, Poll::Question, Legislation::Question, Legislation::Annotation] end end end diff --git a/app/models/annotation.rb b/app/models/annotation.rb index 66abb2db2..295badd92 100644 --- a/app/models/annotation.rb +++ b/app/models/annotation.rb @@ -1,7 +1,7 @@ class Annotation < ActiveRecord::Base serialize :ranges, Array - belongs_to :legislation + belongs_to :legacy_legislation belongs_to :user def permissions diff --git a/app/models/comment.rb b/app/models/comment.rb index cd84a3578..aa3f27e24 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -11,7 +11,7 @@ class Comment < ActiveRecord::Base validates :body, presence: true validates :user, presence: true - validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Budget::Investment", "Poll::Question"] + validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Budget::Investment", "Poll::Question", "Legislation::Question", "Legislation::Annotation"] validate :validate_body_length diff --git a/app/models/comment_notifier.rb b/app/models/comment_notifier.rb index 3cceb5c15..68b350e6b 100644 --- a/app/models/comment_notifier.rb +++ b/app/models/comment_notifier.rb @@ -12,7 +12,9 @@ class CommentNotifier private def send_comment_email - Mailer.comment(@comment).deliver_later if email_on_comment? + unless @comment.commentable.is_a?(Legislation::Annotation) + Mailer.comment(@comment).deliver_later if email_on_comment? + end end def send_reply_email diff --git a/app/models/legacy_legislation.rb b/app/models/legacy_legislation.rb new file mode 100644 index 000000000..ddc267e3a --- /dev/null +++ b/app/models/legacy_legislation.rb @@ -0,0 +1,3 @@ +class LegacyLegislation < ActiveRecord::Base + has_many :annotations +end diff --git a/app/models/legislation.rb b/app/models/legislation.rb index d918c0255..5aa217fd0 100644 --- a/app/models/legislation.rb +++ b/app/models/legislation.rb @@ -1,3 +1,5 @@ -class Legislation < ActiveRecord::Base - has_many :annotations +module Legislation + def self.table_name_prefix + 'legislation_' + end end diff --git a/app/models/legislation/annotation.rb b/app/models/legislation/annotation.rb new file mode 100644 index 000000000..92fa9dd86 --- /dev/null +++ b/app/models/legislation/annotation.rb @@ -0,0 +1,58 @@ +class Legislation::Annotation < ActiveRecord::Base + COMMENTS_PAGE_SIZE = 5 + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + serialize :ranges, Array + + belongs_to :draft_version, class_name: 'Legislation::DraftVersion', foreign_key: 'legislation_draft_version_id' + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + has_many :comments, as: :commentable, dependent: :destroy + + validates :text, presence: true + validates :quote, presence: true + validates :draft_version, presence: true + validates :author, presence: true + + before_save :store_range, :store_context + after_create :create_first_comment + + def store_range + self.range_start = ranges.first["start"] + self.range_start_offset = ranges.first["startOffset"] + self.range_end = ranges.first["end"] + self.range_end_offset = ranges.first["endOffset"] + end + + def store_context + begin + html = draft_version.body_html + doc = Nokogiri::HTML(html) + + selector_start = "/html/body/#{range_start}" + el_start = doc.at_xpath(selector_start) + + selector_end = "/html/body/#{range_end}" + el_end = doc.at_xpath(selector_end) + + remainder_el_start = el_start.text[0 .. range_start_offset-1] unless range_start_offset.zero? + remainder_el_end = el_end.text[range_end_offset .. -1] + + self.context = "#{remainder_el_start}#{quote}#{remainder_el_end}" + rescue + "#{quote}" + end + end + + def create_first_comment + comments.create(body: self.text, user: self.author) + end + + def title + text[0..50] + end + + def weight + comments_count + comments.sum(:cached_votes_total) + end +end diff --git a/app/models/legislation/answer.rb b/app/models/legislation/answer.rb new file mode 100644 index 000000000..cd415d0d4 --- /dev/null +++ b/app/models/legislation/answer.rb @@ -0,0 +1,12 @@ +class Legislation::Answer < ActiveRecord::Base + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :question, class_name: 'Legislation::Question', foreign_key: 'legislation_question_id', inverse_of: :answers, counter_cache: true + belongs_to :question_option, class_name: 'Legislation::QuestionOption', foreign_key: 'legislation_question_option_id', inverse_of: :answers, counter_cache: true + belongs_to :user, dependent: :destroy, inverse_of: :legislation_answers + + validates :question, presence: true, uniqueness: { scope: :user_id} + validates :question_option, presence: true + validates :user, presence: true +end diff --git a/app/models/legislation/draft_version.rb b/app/models/legislation/draft_version.rb new file mode 100644 index 000000000..7b9e6a0aa --- /dev/null +++ b/app/models/legislation/draft_version.rb @@ -0,0 +1,33 @@ +class Legislation::DraftVersion < ActiveRecord::Base + VALID_STATUSES = %w(draft published) + + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :process, class_name: 'Legislation::Process', foreign_key: 'legislation_process_id' + has_many :annotations, class_name: 'Legislation::Annotation', foreign_key: 'legislation_draft_version_id', dependent: :destroy + + validates :title, presence: true + validates :body, presence: true + validates :status, presence: true, inclusion: { in: VALID_STATUSES } + + scope :published, -> { where(status: 'published').order('id DESC') } + + before_save :render_html + + def render_html + renderer = Redcarpet::Render::HTML.new(with_toc_data: true) + toc_renderer = Redcarpet::Render::HTML_TOC.new(with_toc_data: true) + + self.body_html = Redcarpet::Markdown.new(renderer).render(body) + self.toc_html = Redcarpet::Markdown.new(toc_renderer).render(body) + end + + def display_title + status == 'draft' ? "#{title} *" : title + end + + def total_comments + annotations.sum(:comments_count) + end +end diff --git a/app/models/legislation/process.rb b/app/models/legislation/process.rb new file mode 100644 index 000000000..32468b834 --- /dev/null +++ b/app/models/legislation/process.rb @@ -0,0 +1,90 @@ +class Legislation::Process < ActiveRecord::Base + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + has_many :draft_versions, -> { order(:id) }, class_name: 'Legislation::DraftVersion', foreign_key: 'legislation_process_id', dependent: :destroy + has_one :final_draft_version, -> { where final_version: true, status: 'published' }, class_name: 'Legislation::DraftVersion', foreign_key: 'legislation_process_id' + has_many :questions, -> { order(:id) }, class_name: 'Legislation::Question', foreign_key: 'legislation_process_id', dependent: :destroy + + validates :title, presence: true + validates :start_date, presence: true + validates :end_date, presence: true + validates :debate_start_date, presence: true, if: :debate_end_date? + validates :debate_end_date, presence: true, if: :debate_start_date? + validates :allegations_start_date, presence: true, if: :allegations_end_date? + validates :allegations_end_date, presence: true, if: :allegations_start_date? + validate :valid_date_ranges + + scope :open, -> { where("start_date <= ? and end_date >= ?", Date.current, Date.current).order('id DESC') } + scope :next, -> { where("start_date > ?", Date.current).order('id DESC') } + scope :past, -> { where("end_date < ?", Date.current).order('id DESC') } + + def open_phase?(phase) + today = Date.current + + case phase + when :debate + active_phase?(:debate) && today >= debate_start_date && today <= debate_end_date + when :draft_publication + active_phase?(:draft_publication) && today >= draft_publication_date + when :allegations + active_phase?(:allegations) && today >= allegations_start_date && today <= allegations_end_date + when :final_version_publication + active_phase?(:final_version_publication) && today >= final_publication_date + end + end + + def show_phase?(phase) + # show past phases even if they're finished + today = Date.current + + case phase + when :debate + active_phase?(:debate) && today >= debate_start_date + when :draft_publication + active_phase?(:draft_publication) && today >= draft_publication_date + when :allegations + active_phase?(:allegations) && today >= allegations_start_date + when :final_version_publication + active_phase?(:final_version_publication) && today >= final_publication_date + end + end + + def active_phase?(phase) + case phase + when :debate + debate_start_date.present? && debate_end_date.present? + when :draft_publication + draft_publication_date.present? + when :allegations + allegations_start_date.present? && allegations_end_date.present? + when :final_version_publication + final_publication_date.present? + end + end + + def total_comments + questions.sum(:comments_count) + draft_versions.map(&:total_comments).sum + end + + def status + today = Date.current + + if today < start_date + :planned + elsif end_date < today + :closed + else + :open + end + end + + private + + def valid_date_ranges + errors.add(:end_date, :invalid_date_range) if end_date && start_date && end_date < start_date + errors.add(:debate_end_date, :invalid_date_range) if debate_end_date && debate_start_date && debate_end_date < debate_start_date + errors.add(:allegations_end_date, :invalid_date_range) if allegations_end_date && allegations_start_date && allegations_end_date < allegations_start_date + end + +end diff --git a/app/models/legislation/question.rb b/app/models/legislation/question.rb new file mode 100644 index 000000000..374c43eb5 --- /dev/null +++ b/app/models/legislation/question.rb @@ -0,0 +1,42 @@ +class Legislation::Question < ActiveRecord::Base + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :process, class_name: 'Legislation::Process', foreign_key: 'legislation_process_id' + + has_many :question_options, -> { order(:id) }, class_name: 'Legislation::QuestionOption', foreign_key: 'legislation_question_id', dependent: :destroy, inverse_of: :question + has_many :answers, class_name: 'Legislation::Answer', foreign_key: 'legislation_question_id', dependent: :destroy, inverse_of: :question + has_many :comments, as: :commentable, dependent: :destroy + + accepts_nested_attributes_for :question_options, :reject_if => proc { |attributes| attributes[:value].blank? }, allow_destroy: true + + validates :process, presence: true + validates :title, presence: true + + scope :sorted, -> { order('id ASC') } + + def next_question_id + @next_question_id ||= process.questions.where("id > ?", id).sorted.limit(1).pluck(:id).first + end + + def first_question_id + @first_question_id ||= process.questions.sorted.limit(1).pluck(:id).first + end + + def answer_for_user(user) + answers.where(user: user).first + end + + def comments_for_verified_residents_only? + true + end + + def comments_closed? + !comments_open? + end + + def comments_open? + process.open_phase?(:debate) + end +end diff --git a/app/models/legislation/question_option.rb b/app/models/legislation/question_option.rb new file mode 100644 index 000000000..f7927dd1a --- /dev/null +++ b/app/models/legislation/question_option.rb @@ -0,0 +1,10 @@ +class Legislation::QuestionOption < ActiveRecord::Base + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :question, class_name: 'Legislation::Question', foreign_key: 'legislation_question_id', inverse_of: :question_options + has_many :answers, class_name: 'Legislation::Answer', foreign_key: 'legislation_question_id', dependent: :destroy, inverse_of: :question + + validates :question, presence: true + validates :value, presence: true, uniqueness: { scope: :legislation_question_id } +end diff --git a/app/models/user.rb b/app/models/user.rb index c3038c88a..89bc4ce87 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,6 +27,7 @@ class User < ActiveRecord::Base has_many :notifications has_many :direct_messages_sent, class_name: 'DirectMessage', foreign_key: :sender_id has_many :direct_messages_received, class_name: 'DirectMessage', foreign_key: :receiver_id + has_many :legislation_answers, class_name: 'Legislation::Answer', dependent: :destroy, inverse_of: :user belongs_to :geozone validates :username, presence: true, if: :username_required? diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index 0b9929f46..642248a17 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -117,6 +117,26 @@ + <% if feature?(:legislation) %> +
  • > + <%= link_to admin_legislation_processes_path do %> + <%= t("admin.menu.legislation") %> + <% end %> +
  • + <% end %> + +
  • > + <%= link_to admin_banners_path do %> + <%= t("admin.menu.banner") %> + <% end %> +
  • + +
  • > + <%= link_to admin_users_path do %> + <%= t("admin.menu.hidden_users") %> + <% end %> +
  • +
  • diff --git a/app/views/admin/legislation/draft_versions/_form.html.erb b/app/views/admin/legislation/draft_versions/_form.html.erb new file mode 100644 index 000000000..36a774f23 --- /dev/null +++ b/app/views/admin/legislation/draft_versions/_form.html.erb @@ -0,0 +1,94 @@ +<%= form_for [:admin, @process, @draft_version], url: url, html: {data: {watch_changes: true}} do |f| %> + + <% if @draft_version.errors.any? %> + +
    + + + + <%= @draft_version.errors.count %> + <%= t("admin.legislation.draft_versions.errors.form.error", count: @draft_version.errors.count) %> + +
    + +<% end %> + +
    +
    + <%= f.label :title %> +
    +
    + <%= f.text_field :title, label: false, placeholder: t('admin.legislation.draft_versions.form.title_placeholder') %> +
    +
    + +
    +
    + <%= f.label :changelog %> + <%= t('admin.legislation.draft_versions.form.use_markdown') %> +
    +
    + <%= f.text_area :changelog, label: false, rows: 5, placeholder: t('admin.legislation.draft_versions.form.changelog_placeholder') %> +
    +
    + +
    +
    + <%= f.label :status %> +
    +
    + <% ::Legislation::DraftVersion::VALID_STATUSES.each do |status| %> + <%= f.radio_button :status, status, label: false %> + <%= f.label "status_#{status}", t("admin.legislation.draft_versions.statuses.#{status}") %> + <%= t("admin.legislation.draft_versions.form.hints.status.#{status}") %> +
    + <% end %> +
    +
    + +
    +
    + <%= f.label :final_version %> +
    +
    + <%= f.check_box :final_version, label: false %> + <%= t("admin.legislation.draft_versions.form.hints.final_version") %> +
    +
    + +
    +
    + <%= f.label :body %> + <%= t('admin.legislation.draft_versions.form.use_markdown') %> +
    +
    +
    +
    <%= t('admin.legislation.draft_versions.form.title_html', draft_version_title: @draft_version.title, process_title: @process.title ) %>
    + +
    + <%= f.submit(class: "button", value: t("admin.legislation.draft_versions.#{admin_submit_action(@draft_version)}.submit_button")) %> +
    + + <%= link_to "#", class: 'fullscreen-toggle' do %> + " + data-open-text="<%= t("admin.legislation.draft_versions.form.close_text_editor")%>"> + <%= t("admin.legislation.draft_versions.form.launch_text_editor")%> + + <% end %> +
    +
    + <%= f.text_area :body, label: false, placeholder: t('admin.legislation.draft_versions.form.body_placeholder') %> +
    +
    +
    +
    +
    + +
    +
    + <%= f.submit(class: "button expanded", value: t("admin.legislation.draft_versions.#{admin_submit_action(@draft_version)}.submit_button")) %> +
    +
    +<% end %> diff --git a/app/views/admin/legislation/draft_versions/edit.html.erb b/app/views/admin/legislation/draft_versions/edit.html.erb new file mode 100644 index 000000000..5ad6d9501 --- /dev/null +++ b/app/views/admin/legislation/draft_versions/edit.html.erb @@ -0,0 +1,36 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= @process.title %> - <%= t("admin.legislation.draft_versions.index.title") %> - <%= @draft_version.title %> +<% end %> + +
    +
    + <%= link_to admin_legislation_process_draft_versions_path(@process), class: "back" do %> + + <%= t("admin.legislation.draft_versions.edit.back") %> + <% end %> + +

    <%= @process.title %>

    + + <%= render 'admin/legislation/processes/subnav', process: @process, active: 'draft_versions' %> + +
    +
    + +
    + +
    +

    <%= @draft_version.title %>

    +
    +
    + <%= link_to t("admin.legislation.draft_versions.index.delete"), + admin_legislation_process_draft_version_path(@process, @draft_version), + method: :delete, + class: 'button hollow alert' %> +
    +
    + + <%= render 'form', url: admin_legislation_process_draft_version_path(@process, @draft_version) %> +
    +
    diff --git a/app/views/admin/legislation/draft_versions/index.html.erb b/app/views/admin/legislation/draft_versions/index.html.erb new file mode 100644 index 000000000..bfe7d33f6 --- /dev/null +++ b/app/views/admin/legislation/draft_versions/index.html.erb @@ -0,0 +1,51 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= @process.title %> - <%= t("admin.legislation.draft_versions.index.title") %> +<% end %> + +
    +
    + <%= link_to admin_legislation_processes_path, class: "back" do %> + + <%= t("admin.legislation.processes.edit.back") %> + <% end %> + +

    <%= @process.title %>

    + + <%= render 'admin/legislation/processes/subnav', process: @process, active: 'draft_versions' %> + +
    +
    +

    <%= t("admin.legislation.draft_versions.index.title") %>

    +
    + +
    + <%= link_to t("admin.legislation.draft_versions.index.create"), new_admin_legislation_process_draft_version_path, class: "button" %> +
    +
    + + + + + + + + + + + + + <% @process.draft_versions.each do |draft_version| %> + + + + + + + + <% end %> + +
    <%= t("admin.legislation.draft_versions.table.title") %><%= t("admin.legislation.draft_versions.table.created_at") %><%= t("admin.legislation.draft_versions.table.status") %><%= t("admin.legislation.draft_versions.table.comments") %><%= t("admin.legislation.draft_versions.table.final_version") %>
    + <%= link_to draft_version.title, edit_admin_legislation_process_draft_version_path(@process, draft_version) %> + <%= draft_version.created_at.to_date %><%= draft_version.status %> <%= link_to "(#{t('.preview')})", legislation_process_draft_version_path(@process, draft_version) if draft_version.status == 'draft' %><%= draft_version.total_comments %><%= draft_version.final_version %>
    +
    +
    diff --git a/app/views/admin/legislation/draft_versions/new.html.erb b/app/views/admin/legislation/draft_versions/new.html.erb new file mode 100644 index 000000000..b43bfc276 --- /dev/null +++ b/app/views/admin/legislation/draft_versions/new.html.erb @@ -0,0 +1,24 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= @process.title %> - <%= t("admin.legislation.draft_versions.index.title") %> - <%= t("admin.legislation.draft_versions.new.title") %> +<% end %> + +
    +
    + <%= link_to admin_legislation_process_draft_versions_path(@process), class: "back" do %> + + <%= t("admin.legislation.draft_versions.new.back") %> + <% end %> + +

    <%= @process.title %>

    + + <%= render 'admin/legislation/processes/subnav', process: @process, active: 'draft_versions' %> + +
    +
    +

    <%= t("admin.legislation.draft_versions.new.title") %>

    +
    +
    + + <%= render 'form', url: admin_legislation_process_draft_versions_path(@process) %> +
    +
    diff --git a/app/views/admin/legislation/processes/_form.html.erb b/app/views/admin/legislation/processes/_form.html.erb new file mode 100644 index 000000000..d78675890 --- /dev/null +++ b/app/views/admin/legislation/processes/_form.html.erb @@ -0,0 +1,202 @@ +<%= form_for [:admin, @process], html: {data: {watch_changes: true}} do |f| %> + + <% if @process.errors.any? %> + +
    + + + + <%= @process.errors.count %> + <%= t("admin.legislation.processes.errors.form.error", count: @process.errors.count) %> + +
    + + <% end %> + +
    +
    + +
    +
    + <%= t('admin.legislation.processes.form.start') %> +
    +
    + <%= f.text_field :start_date, + label: false, + value: format_date_for_calendar_form(@process.start_date), + class: "js-calendar-full", + id: "start_date" %> +
    +
    + <%= t('admin.legislation.processes.form.end') %> +
    +
    + <%= f.text_field :end_date, + label: false, + value: format_date_for_calendar_form(@process.end_date), + class: "js-calendar-full", + id: "end_date" %> +
    + +
    + +
    +
    + <%= t('admin.legislation.processes.form.start') %> +
    +
    + <%= f.text_field :debate_start_date, + label: false, + value: format_date_for_calendar_form(@process.debate_start_date), + class: "js-calendar-full", + id: "debate_start_date" %> +
    +
    + <%= t('admin.legislation.processes.form.end') %> +
    +
    + <%= f.text_field :debate_end_date, + label: false, + value: format_date_for_calendar_form(@process.debate_end_date), + class: "js-calendar-full", + id: "debate_end_date" %> +
    +
    + <%= check_box_tag :debate_phase_active, @process.active_phase?(:debate), @process.new_record? || @process.active_phase?(:debate), data: {disable_date: "debate"} %> + <%= label_tag :debate_phase_active, t('admin.legislation.processes.form.active') %> +
    + +
    + +
    +
    + <%= t('admin.legislation.processes.form.start') %> +
    +
    + <%= f.text_field :allegations_start_date, + label: false, + value: format_date_for_calendar_form(@process.allegations_start_date), + class: "js-calendar-full", + id: "allegations_start_date" %> +
    +
    + <%= t('admin.legislation.processes.form.end') %> +
    +
    + <%= f.text_field :allegations_end_date, + label: false, + value: format_date_for_calendar_form(@process.allegations_end_date), + class: "js-calendar-full", + id: "allegations_end_date" %> +
    +
    + <%= check_box_tag :allegations_phase_active, @process.active_phase?(:allegations), @process.new_record? || @process.active_phase?(:allegations), data: {disable_date: "allegations"} %> + <%= label_tag :allegations_phase_active, t('admin.legislation.processes.form.active') %> +
    +
    + +
    +
    + <%= f.label :draft_publication_date %> +
    +
    + <%= f.text_field :draft_publication_date, + label: false, + value: format_date_for_calendar_form(@process.draft_publication_date), + class: "js-calendar-full", + id: "draft_publication_date" %> +
    +
    + <%= check_box_tag :draft_publication_phase_active, @process.active_phase?(:draft_publication), @process.new_record? || @process.active_phase?(:draft_publication), data: {disable_date: "draft_publication"} %> + <%= label_tag :draft_publication_phase_active, t('admin.legislation.processes.form.active') %> +
    +
    + +
    +
    + <%= f.label :final_publication_date %> +
    +
    + <%= f.text_field :final_publication_date, + label: false, + value: format_date_for_calendar_form(@process.final_publication_date), + class: "js-calendar-full", + id: "final_publication_date" %> +
    +
    + <%= check_box_tag :final_version_publication_phase_active, @process.active_phase?(:final_version_publication), @process.new_record? || @process.active_phase?(:final_version_publication), data: {disable_date: "final_publication"} %> + <%= label_tag :final_version_publication_phase_active, t('admin.legislation.processes.form.active') %> +
    +
    + +
    +
    + <%= f.label :title %> +
    +
    + <%= f.text_field :title, + label: false, + placeholder: t('admin.legislation.processes.form.title_placeholder') %> +
    +
    + +
    +
    + <%= f.label :description %> + <%= t('admin.legislation.processes.form.use_markdown') %> +
    +
    + <%= f.text_area :description, + label: false, + rows: 5, + placeholder: t('admin.legislation.processes.form.description_placeholder') %> +
    +
    + +
    +
    + <%= f.label :target %> + <%= t('admin.legislation.processes.form.use_markdown') %> +
    +
    + <%= f.text_area :target, + label: false, + rows: 5, + placeholder: t('admin.legislation.processes.form.target_placeholder') %> +
    +
    + +
    +
    + <%= f.label :how_to_participate %> + <%= t('admin.legislation.processes.form.use_markdown') %> +
    +
    + <%= f.text_area :how_to_participate, + label: false, + rows: 5, + placeholder: t('admin.legislation.processes.form.how_to_participate_placeholder') %> +
    +
    + +
    +
    + <%= f.label :additional_info %> + <%= t('admin.legislation.processes.form.use_markdown') %> +
    +
    + <%= f.text_area :additional_info, + label: false, + rows: 10, + placeholder: t('admin.legislation.processes.form.additional_info_placeholder') %> +
    +
    + +
    +
    + <%= f.submit(class: "button expanded", value: t("admin.legislation.processes.#{admin_submit_action(@process)}.submit_button")) %> +
    +
    +<% end %> diff --git a/app/views/admin/legislation/processes/_subnav.html.erb b/app/views/admin/legislation/processes/_subnav.html.erb new file mode 100644 index 000000000..97640c4f4 --- /dev/null +++ b/app/views/admin/legislation/processes/_subnav.html.erb @@ -0,0 +1,11 @@ + diff --git a/app/views/admin/legislation/processes/edit.html.erb b/app/views/admin/legislation/processes/edit.html.erb new file mode 100644 index 000000000..4bf418d06 --- /dev/null +++ b/app/views/admin/legislation/processes/edit.html.erb @@ -0,0 +1,19 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= @process.title %> +<% end %> + +
    + +
    + <%= link_to admin_legislation_processes_path, class: "back" do %> + + <%= t("admin.legislation.processes.edit.back") %> + <% end %> + +

    <%= @process.title %>

    + + <%= render 'subnav', process: @process, active: 'info' %> + + <%= render "form" %> +
    +
    diff --git a/app/views/admin/legislation/processes/index.html.erb b/app/views/admin/legislation/processes/index.html.erb new file mode 100644 index 000000000..a4a0a7f3e --- /dev/null +++ b/app/views/admin/legislation/processes/index.html.erb @@ -0,0 +1,50 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= t("admin.legislation.processes.index.filters.#{@current_filter}") %> +<% end %> + +
    +
    +
    +

    <%= t("admin.legislation.processes.index.title") %>

    +
    +
    + <%= link_to t("admin.legislation.processes.index.create"), new_admin_legislation_process_path, class: "button" %> +
    +
    + + <%= render 'shared/filter_subnav', i18n_namespace: "admin.legislation.processes.index" %> + +

    <%= page_entries_info @processes %>

    + + + + + + + + + + + + + <% @processes.each do |process| %> + + + + + + + + <% end %> + +
    <%= t("admin.legislation.processes.process.title") %><%= t("admin.legislation.processes.process.status") %><%= t("admin.legislation.processes.process.creation_date") %><%= t("admin.legislation.processes.process.comments") %>
    + <%= link_to process.title, edit_admin_legislation_process_path(process) %> + <%= t("admin.legislation.processes.process.status_#{process.status}") %><%= I18n.l process.created_at.to_date %><%= process.total_comments %> + <%= link_to t("admin.legislation.processes.index.delete"), admin_legislation_process_path(process), + method: :delete, + class: 'button hollow alert' %> +
    + + <%= paginate @processes %> + +
    diff --git a/app/views/admin/legislation/processes/new.html.erb b/app/views/admin/legislation/processes/new.html.erb new file mode 100644 index 000000000..3fed34799 --- /dev/null +++ b/app/views/admin/legislation/processes/new.html.erb @@ -0,0 +1,17 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= t("admin.legislation.processes.new.title") %> +<% end %> + +
    + +
    + <%= link_to admin_legislation_processes_path, class: "back" do %> + + <%= t("admin.legislation.processes.new.back") %> + <% end %> + +

    <%= t("admin.legislation.processes.new.title") %>

    + + <%= render "form" %> +
    +
    diff --git a/app/views/admin/legislation/questions/_form.html.erb b/app/views/admin/legislation/questions/_form.html.erb new file mode 100644 index 000000000..4b0cf8ce5 --- /dev/null +++ b/app/views/admin/legislation/questions/_form.html.erb @@ -0,0 +1,44 @@ +<%= form_for [:admin, @process, @question], url: url, html: {data: {watch_changes: true}} do |f| %> + + <% if @question.errors.any? %> + +
    + + + + <%= @question.errors.count %> + <%= t("admin.legislation.questions.errors.form.error", count: @question.errors.count) %> + +
    + +<% end %> + + +
    + <%= f.label :title %> +
    +
    + <%= f.text_area :title, rows: 5, label: false, placeholder: t('admin.legislation.questions.form.title_placeholder') %> +
    + +
    + <%= f.label :question_options %> +
    +
    + <%= f.fields_for :question_options do |ff| %> + <%= render 'question_option_fields', f: ff %> + <% end %> +
    +
    + <%= link_to_add_association t('.add_option'), f, :question_options %> +
    + + +
    +
    + <%= f.submit(class: "button expanded", value: t("admin.legislation.questions.#{admin_submit_action(@question)}.submit_button")) %> +
    +
    +<% end %> diff --git a/app/views/admin/legislation/questions/_question_option_fields.html.erb b/app/views/admin/legislation/questions/_question_option_fields.html.erb new file mode 100644 index 000000000..ef77872fa --- /dev/null +++ b/app/views/admin/legislation/questions/_question_option_fields.html.erb @@ -0,0 +1,11 @@ +
    +
    +
    +
    + <%= f.text_field :value, label: false, placeholder: t('admin.legislation.questions.form.value_placeholder') %> +
    +
    + <%= link_to_remove_association " #{t('.remove_option')}".html_safe, f %> +
    +
    +
    diff --git a/app/views/admin/legislation/questions/edit.html.erb b/app/views/admin/legislation/questions/edit.html.erb new file mode 100644 index 000000000..47b44b11a --- /dev/null +++ b/app/views/admin/legislation/questions/edit.html.erb @@ -0,0 +1,29 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= @process.title %> - <%= t("admin.legislation.questions.index.title") %> - <%= @question.title %> +<% end %> + +
    +
    + <%= link_to admin_legislation_process_questions_path(@process), class: "back" do %> + + <%= t("admin.legislation.questions.edit.back") %> + <% end %> + +

    <%= @process.title %>

    + + <%= render 'admin/legislation/processes/subnav', process: @process, active: 'questions' %> + +
    +
    +

    <%= t("admin.legislation.questions.edit.title", question_title: @question.title) %>

    +
    +
    + <%= link_to t("admin.legislation.questions.index.delete"), admin_legislation_process_question_path(@process, @question), + method: :delete, + class: 'button hollow alert' %> +
    +
    + + <%= render 'form', url: admin_legislation_process_question_path(@process, @question) %> +
    +
    diff --git a/app/views/admin/legislation/questions/index.html.erb b/app/views/admin/legislation/questions/index.html.erb new file mode 100644 index 000000000..4e82de910 --- /dev/null +++ b/app/views/admin/legislation/questions/index.html.erb @@ -0,0 +1,57 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= @process.title %> - <%= t("admin.legislation.questions.index.title") %> +<% end %> + +
    +
    + <%= link_to admin_legislation_processes_path, class: "back" do %> + + <%= t("admin.legislation.questions.index.back") %> + <% end %> + +

    <%= @process.title %>

    + + <%= render 'admin/legislation/processes/subnav', process: @process, active: 'questions' %> + +
    +
    +

    <%= t("admin.legislation.questions.index.title") %>

    +
    + +
    + <%= link_to t("admin.legislation.questions.index.create"), new_admin_legislation_process_question_path, class: "button" %> +
    +
    + + + + + + + + + + + + <% @process.questions.each do |question| %> + + + + + + + <% end %> + +
    <%= t("admin.legislation.questions.table.title") %><%= t("admin.legislation.questions.table.question_options") %><%= t("admin.legislation.questions.table.answers_count") %><%= t("admin.legislation.questions.table.comments_count") %>
    + <%= link_to question.title, edit_admin_legislation_process_question_path(@process, question) %> + + <%= content_tag :ul do %> + <% question.question_options.each do |question_option| %> + <%= content_tag :li do %> + <%= question_option.value %> (<%= question_option.answers_count %>) + <% end %> + <% end %> + <% end %> + <%= question.answers_count %><%= link_to question.comments.count, legislation_process_question_path(@process, question, anchor: 'comments') %>
    +
    +
    diff --git a/app/views/admin/legislation/questions/new.html.erb b/app/views/admin/legislation/questions/new.html.erb new file mode 100644 index 000000000..243815aff --- /dev/null +++ b/app/views/admin/legislation/questions/new.html.erb @@ -0,0 +1,24 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.legislation") %> - <%= @process.title %> - <%= t("admin.legislation.questions.index.title") %> - <%= t("admin.legislation.questions.new.title") %> +<% end %> + +
    +
    + <%= link_to admin_legislation_process_questions_path(@process), class: "back" do %> + + <%= t("admin.legislation.questions.new.back") %> + <% end %> + +

    <%= @process.title %>

    + + <%= render 'admin/legislation/processes/subnav', process: @process, active: 'questions' %> + +
    +
    +

    <%= t("admin.legislation.questions.new.title") %>

    +
    +
    + + <%= render 'form', url: admin_legislation_process_questions_path(@process) %> +
    +
    diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb index 4960baba8..969995ee0 100644 --- a/app/views/comments/_comment.html.erb +++ b/app/views/comments/_comment.html.erb @@ -81,7 +81,7 @@ <%= t("comments.comment.responses", count: 0) %> <% end %> - <% if user_signed_in? %> + <% if user_signed_in? && !comments_closed_for_commentable?(comment.commentable) && !require_verified_resident_for_commentable?(comment.commentable, current_user) %>  |  <%= link_to(comment_link_text(comment), "", class: "js-add-comment-link", data: {'id': dom_id(comment)}) %> diff --git a/app/views/comments/_comment_tree.html.erb b/app/views/comments/_comment_tree.html.erb index 0581809fb..c72bd8bcf 100644 --- a/app/views/comments/_comment_tree.html.erb +++ b/app/views/comments/_comment_tree.html.erb @@ -6,21 +6,32 @@

    - <%= t("debates.show.comments_title") %> + <%= comment_tree_title_text(commentable) %> (<%= commentable.comments_count %>)

    <%= render 'shared/wide_order_selector', i18n_namespace: "comments" %> <% if user_signed_in? %> - <%= render 'comments/form', {commentable: commentable, parent_id: nil, toggeable: false} %> + <% if comments_closed_for_commentable?(commentable) %> +
    +
    + <%= comments_closed_text(commentable) %> +
    + <% elsif require_verified_resident_for_commentable?(commentable, current_user) %> +
    +
    + <%= t("comments.verified_only", verify_account: link_to(t("comments.verify_account"), verification_path )).html_safe %> +
    + <% else %> + <%= render 'comments/form', {commentable: commentable, parent_id: nil, toggeable: false} %> + <% end %> <% else %> -
    - -
    - <%= t("debates.show.login_to_comment", - signin: link_to(t("votes.signin"), new_user_session_path), - signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %> +
    +
    + <%= t("debates.show.login_to_comment", + signin: link_to(t("votes.signin"), new_user_session_path), + signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %>
    <% end %> diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb index e3399c383..9d9767e73 100644 --- a/app/views/comments/_form.html.erb +++ b/app/views/comments/_form.html.erb @@ -2,13 +2,13 @@ <% css_id = parent_or_commentable_dom_id(parent_id, commentable) %>
    class="comment-form"> <%= form_for Comment.new, remote: true do |f| %> - <%= label_tag "comment-body-#{css_id}", t("comments.form.leave_comment") %> + <%= label_tag "comment-body-#{css_id}", leave_comment_text(commentable) %> <%= f.text_area :body, id: "comment-body-#{css_id}", maxlength: Comment.body_max_length, label: false %> <%= f.hidden_field :commentable_type, value: commentable.class.name %> <%= f.hidden_field :commentable_id, value: commentable.id %> <%= f.hidden_field :parent_id, value: parent_id %> - <%= f.submit comment_button_text(parent_id), class: "button" %> + <%= f.submit comment_button_text(parent_id, commentable), class: "button" %> <% if can? :comment_as_moderator, commentable %>
    diff --git a/app/views/layouts/_flash.html.erb b/app/views/layouts/_flash.html.erb index 9f55b449a..a0f129224 100644 --- a/app/views/layouts/_flash.html.erb +++ b/app/views/layouts/_flash.html.erb @@ -5,7 +5,7 @@
    - <%= flash_message %> + <%= flash_message.try(:html_safe) %>
    diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index 198bf3adf..2352c5181 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -13,7 +13,7 @@ <%= favicon_link_tag "favicon.ico" %> - +
    diff --git a/app/views/legacy_legislations/show.html.erb b/app/views/legacy_legislations/show.html.erb new file mode 100644 index 000000000..ab65649bb --- /dev/null +++ b/app/views/legacy_legislations/show.html.erb @@ -0,0 +1,29 @@ + + +
    +
    +
    +

    <%= @legacy_legislation.title %>

    +
    <%= @legacy_legislation.body %>
    +
    +
    +
    diff --git a/app/views/legislation/annotations/_comments_box.html.erb b/app/views/legislation/annotations/_comments_box.html.erb new file mode 100644 index 000000000..a7b7c3ca7 --- /dev/null +++ b/app/views/legislation/annotations/_comments_box.html.erb @@ -0,0 +1,76 @@ +
    +
    + +
    <%= t('legislation.annotations.comments.comments_count', count: annotation.comments.roots.count) %>
    + <%= link_to legislation_process_draft_version_annotation_path(annotation.draft_version.process, annotation.draft_version, annotation) do %> + + <% end %> +
    +
    + <% annotation.comments.roots.sort_by_most_voted.limit(Legislation::Annotation::COMMENTS_PAGE_SIZE).each do |comment| %> +
    +
    +

    <%= truncate comment.body, length: 250 %>

    +
    +
    +
    + <% if comment.body.length > 250 %> +
    + <%= link_to legislation_process_draft_version_annotation_path(annotation.draft_version.process, annotation.draft_version, annotation) do %> + <%= t('legislation.annotations.comments.see_complete') %> + <% end %> +
    + <% end %> +
    + <%= link_to legislation_process_draft_version_annotation_path(annotation.draft_version.process, annotation.draft_version, annotation, anchor: "comment_#{comment.id}") do %> + <%= t('legislation.annotations.comments.replies_count', count: comment.children.size) %> + <% end %> +
    +
    +
    +
    + <%= render 'comments/votes', comment: comment %> +
    +
    +
    +
    + <% end %> + +
    + +
    +
    diff --git a/app/views/legislation/annotations/_comments_box_form.js.erb b/app/views/legislation/annotations/_comments_box_form.js.erb new file mode 100644 index 000000000..a475c54db --- /dev/null +++ b/app/views/legislation/annotations/_comments_box_form.js.erb @@ -0,0 +1,15 @@ +$('#comments-box-<%= annotation.id %> a.publish-comment').on({ + click: function(e) { + e.preventDefault(); + $('a.publish-comment').hide(); + $('#js-comment-form-annotation-<%= annotation.id %>').toggle(); + $('#js-comment-form-annotation-<%= annotation.id %> textarea').focus(); + return; + } +}); + +<% if comment.errors.any? %> + $('#comments-box-<%= @annotation.id %> a.publish-comment').hide(); + $('#js-comment-form-annotation-<%= annotation.id %>').toggle(); + $('#js-comment-form-annotation-<%= annotation.id %> textarea').focus(); +<% end %> diff --git a/app/views/legislation/annotations/_form.html.erb b/app/views/legislation/annotations/_form.html.erb new file mode 100644 index 000000000..676991859 --- /dev/null +++ b/app/views/legislation/annotations/_form.html.erb @@ -0,0 +1,35 @@ +
    +
    + +
    <%= t('legislation.annotations.comments.comments_count', count: 0) %>
    +
    + +
    +
    + <% if !@process.open_phase?(:allegations) %> +
    + <%= t("legislation.annotations.form.phase_not_open") %> +
    + <% elsif user_signed_in? %> + <%= form_for Legislation::Annotation.new, url: legislation_process_draft_version_annotations_path(@process, @draft_version), remote: true do |f| %> + <%= f.text_area :text %> + +
    + <%= t('legislation.annotations.comments.cancel') %> + <%= f.submit t('legislation.annotations.comments.publish_comment'), class: 'button strong publish-comment' %> +
    + + <%= f.hidden_field :quote %> + <%= f.hidden_field :ranges %> + <% end %> + <% else %> +
    + <%= t("legislation.annotations.form.login_to_comment", + signin: link_to(t("legislation.annotations.form.signin"), new_user_session_path), + signup: link_to(t("legislation.annotations.form.signup"), new_user_registration_path)).html_safe %> +
    + <% end %> +
    +
    +
    + diff --git a/app/views/legislation/annotations/_slim_version_chooser.html.erb b/app/views/legislation/annotations/_slim_version_chooser.html.erb new file mode 100644 index 000000000..a3288597e --- /dev/null +++ b/app/views/legislation/annotations/_slim_version_chooser.html.erb @@ -0,0 +1,24 @@ +
    +
    +

    <%= t('legislation.annotations.version_chooser.seeing_version') %>

    +
    + <%= form_tag go_to_version_legislation_process_draft_versions_path(process), method: :get, id: "draft_version_go_to_version" do %> + <%= select_tag "draft_version_id", options_from_collection_for_select(process.draft_versions.published, 'id', 'display_title', draft_version.id), "aria-label": t('legislation.draft_versions.show.select_draft_version') %> + <%= hidden_field_tag "redirect_action", "annotations" %> + <%= submit_tag t('legislation.draft_versions.show.select_version_submit'), class: "button" %> + <% end %> +
    + <%= t('legislation.draft_versions.show.updated_at', date: format_date(@draft_version.updated_at)) %> +
    +
    + <%= link_to t('legislation.annotations.version_chooser.see_text'), legislation_process_draft_version_path(process, draft_version), title: t('legislation.annotations.version_chooser.see_text'), class: "button strong" %> +
    + +
    diff --git a/app/views/legislation/annotations/_version_chooser.html.erb b/app/views/legislation/annotations/_version_chooser.html.erb new file mode 100644 index 000000000..2b98cd90a --- /dev/null +++ b/app/views/legislation/annotations/_version_chooser.html.erb @@ -0,0 +1,16 @@ +
    +
    +

    <%= t('.seeing_version') %>

    +
    + <%= form_tag go_to_version_legislation_process_draft_versions_path(process), method: :get, id: "draft_version_go_to_version" do %> + <%= select_tag "draft_version_id", options_from_collection_for_select(process.draft_versions.published, 'id', 'display_title', draft_version.id), "aria-label": t('legislation.draft_versions.show.select_draft_version') %> + <%= hidden_field_tag "redirect_action", "annotations" %> + <%= submit_tag t('legislation.draft_versions.show.select_version_submit'), class: "button" %> + <% end %> +
    + <%= t('legislation.draft_versions.show.updated_at', date: format_date(@draft_version.updated_at)) %> +
    +
    + <%= link_to t('.see_text'), legislation_process_draft_version_path(process, draft_version), title: t('.see_text'), class: "button strong" %> +
    +
    diff --git a/app/views/legislation/annotations/comments.js.erb b/app/views/legislation/annotations/comments.js.erb new file mode 100644 index 000000000..d44eff2fd --- /dev/null +++ b/app/views/legislation/annotations/comments.js.erb @@ -0,0 +1,3 @@ +$("#comments-box").append("<%= j render('comments_box', annotation: @annotation) %>").show(); + +<%= render 'comments_box_form', comment: @comment, annotation: @annotation %> diff --git a/app/views/legislation/annotations/index.html.erb b/app/views/legislation/annotations/index.html.erb new file mode 100644 index 000000000..cc67f9f2d --- /dev/null +++ b/app/views/legislation/annotations/index.html.erb @@ -0,0 +1,32 @@ +<% provide :title do %><%= "#{t('.title')} - #{@draft_version.title} - #{@process.title}" %><% end %> + +<%= render 'legislation/processes/header', process: @process, header: :small %> + +<%= render 'legislation/processes/key_dates', process: @process, phase: :allegations %> + +
    +
    + <%= render 'slim_version_chooser', process: @process, draft_version: @draft_version %> +
    + +
    + + <% @annotations.each do |annotation| %> +
    + <%= t('.comments_about') %> + + <%= link_to legislation_process_draft_version_path(@process, @draft_version, anchor: "annotation-id-#{annotation.id}") do %> + <%= t('.see_in_context') %> + <% end %> + +
    + <%= annotation.context.try(:html_safe).presence || annotation.quote %> +
    + <%= link_to legislation_process_draft_version_annotation_path(@process, @draft_version, annotation) do %> + <%= t('.comments_count', count: annotation.comments_count) %> + <% end %> +
    + <% end %> + +
    +
    diff --git a/app/views/legislation/annotations/new.js.erb b/app/views/legislation/annotations/new.js.erb new file mode 100644 index 000000000..9b719814c --- /dev/null +++ b/app/views/legislation/annotations/new.js.erb @@ -0,0 +1,2 @@ +$("#comments-box").html("<%= j render('form') %>"); + diff --git a/app/views/legislation/annotations/new_comment.js.erb b/app/views/legislation/annotations/new_comment.js.erb new file mode 100644 index 000000000..1363acec9 --- /dev/null +++ b/app/views/legislation/annotations/new_comment.js.erb @@ -0,0 +1,3 @@ +$("#comments-box-<%= @annotation.id %>").replaceWith("<%= j render('comments_box', annotation: @annotation) %>").show(); + +<%= render 'comments_box_form', comment: @comment, annotation: @annotation %> diff --git a/app/views/legislation/annotations/show.html.erb b/app/views/legislation/annotations/show.html.erb new file mode 100644 index 000000000..a299d160e --- /dev/null +++ b/app/views/legislation/annotations/show.html.erb @@ -0,0 +1,46 @@ +<% provide :title do %><%= "#{t('.title')} - #{@draft_version.title} - #{@process.title}" %><% end %> + +<%= render 'legislation/processes/header', process: @process, header: :small %> + +<%= render 'legislation/processes/key_dates', process: @process, phase: :allegations %> + +
    +
    + <%= render 'version_chooser', process: @process, draft_version: @draft_version %> + +
    +
    +
    +
    + <%= t('legislation.annotations.index.comments_about') %> + +
    +
    +
    + <%= @annotation.context.try(:html_safe).presence || @annotation.quote %> +
    +
    + + <%= link_to legislation_process_draft_version_path(@process, @draft_version, anchor: "annotation-id-#{@annotation.id}") do %> + <%= t('legislation.annotations.index.see_in_context') %> + <% end %> + +
    +
    +
    +
    + + +
    + + <%= render partial: '/comments/comment_tree', locals: { comment_tree: @comment_tree, comment_flags: @comment_flags } %> +
    +
    +
    diff --git a/app/views/legislation/answers/create.js.erb b/app/views/legislation/answers/create.js.erb new file mode 100644 index 000000000..468b2610b --- /dev/null +++ b/app/views/legislation/answers/create.js.erb @@ -0,0 +1 @@ +$("#legislation-answer-form").html('<%= j render("legislation/questions/answer_form", process: @process, question: @question, answer: @answer) %>'); diff --git a/app/views/legislation/draft_versions/_comments_panel.html.erb b/app/views/legislation/draft_versions/_comments_panel.html.erb new file mode 100644 index 000000000..b7f92074c --- /dev/null +++ b/app/views/legislation/draft_versions/_comments_panel.html.erb @@ -0,0 +1,20 @@ +
    +
    +
    + <%= t('legislation.draft_versions.show.text_comments') %> +
    +
    + +
    + <%= t('legislation.draft_versions.show.text_comments') %> +
    + + +
    diff --git a/app/views/legislation/draft_versions/changes.html.erb b/app/views/legislation/draft_versions/changes.html.erb new file mode 100644 index 000000000..bf243ae70 --- /dev/null +++ b/app/views/legislation/draft_versions/changes.html.erb @@ -0,0 +1,32 @@ +<% provide :title do %><%= "#{@draft_version.title} - #{t('.title')} - #{@process.title}" %><% end %> + +<%= render 'legislation/processes/header', process: @process, header: :small %> + +<%= render 'legislation/processes/key_dates', process: @process, phase: :allegations %> + +
    +
    +
    +
    +

    <%= t('.seeing_changelog_version') %>

    +
    + <%= form_tag go_to_version_legislation_process_draft_versions_path(@process), method: :get, id: "draft_version_go_to_version" do %> + <%= select_tag "draft_version_id", options_from_collection_for_select(@draft_versions_list, 'id', 'display_title', @draft_version.id), "aria-label": t('legislation.draft_versions.show.select_draft_version') %> + <%= hidden_field_tag "redirect_action", "changes" %> + <%= submit_tag t('legislation.draft_versions.show.select_version_submit'), class: "button" %> + <% end %> +
    + <%= t('legislation.draft_versions.show.updated_at', date: format_date(@draft_version.updated_at)) %> +
    +
    + <%= link_to t('.see_text'), legislation_process_draft_version_path(@process, @draft_version), title: t('.see_text'), class: "button strong" %> +
    +
    + +
    +
    + <%= markdown @draft_version.changelog %> +
    +
    +
    +
    diff --git a/app/views/legislation/draft_versions/show.html.erb b/app/views/legislation/draft_versions/show.html.erb new file mode 100644 index 000000000..f602d89ff --- /dev/null +++ b/app/views/legislation/draft_versions/show.html.erb @@ -0,0 +1,78 @@ +<% provide :title do %><%= "#{@draft_version.title} - #{@process.title}" %><% end %> + +<%= render 'legislation/processes/header', process: @process, header: :small %> + +<%= render 'legislation/processes/key_dates', process: @process, phase: :allegations %> + +
    +
    +
    +
    +

    <%= t('.seeing_version') %>

    +
    + <%= form_tag go_to_version_legislation_process_draft_versions_path(@process), method: :get, id: "draft_version_go_to_version" do %> + <%= select_tag "draft_version_id", options_from_collection_for_select(@draft_versions_list, 'id', 'display_title', @draft_version.id), "aria-label": t('.select_draft_version') %> + <%= submit_tag t('.select_version_submit'), class: "button" %> + <% end %> + <% if @draft_version.changelog.present? %> + <%= link_to t('.see_changes'), legislation_process_draft_version_changes_path(@process, @draft_version) %> + <% end %> +
    + <%= t('.updated_at', date: format_date(@draft_version.updated_at)) %> +
    + + <% unless @draft_version.final_version? %> +
    + <%= link_to t('.see_comments'), legislation_process_draft_version_annotations_path(@process, @draft_version), title: t('.see_comments'), class: "button strong" %> +
    + <% end %> + +
    + +
    +
    "> +
    +
    + <%= t('.text_toc') %> +
    +
    + +
    + <%= t('.text_toc') %> +
    + +
    +
    + <%= @draft_version.toc_html.html_safe %> +
    +
    +
    +
    +
    +
    <%= t('.text_body') %>
    +
    +
    + <% if @draft_version.final_version? %> +
    + <% else %> +
    + <% end %> + <%= @draft_version.body_html.html_safe %> +
    +
    +
    + + <% if @draft_version.final_version? %> +
    + <% else %> + <%= render 'comments_panel', draft_version: @draft_version %> + <% end %> + +
    +
    + +
    diff --git a/app/views/legislation/processes/_debate.html.erb b/app/views/legislation/processes/_debate.html.erb new file mode 100644 index 000000000..6bdfc26a0 --- /dev/null +++ b/app/views/legislation/processes/_debate.html.erb @@ -0,0 +1,14 @@ +
    +
    + <% if process.questions.empty? %> +

    <%= t('.empty_questions') %>

    + <% else %> + <%= render process.questions %> + <% end %> +
    +
    + +
    +
    <%= t('.participate') %>
    +
    + diff --git a/app/views/legislation/processes/_header.html.erb b/app/views/legislation/processes/_header.html.erb new file mode 100644 index 000000000..ce9c6eeee --- /dev/null +++ b/app/views/legislation/processes/_header.html.erb @@ -0,0 +1,51 @@ +<% if header == :small %> +
    +
    +
    +

    <%= process.title %>

    +
    +
    +
    +
    + +
    +
    + <% if process.description.present? %> +

    <%= t('legislation.processes.header_full.description') %>

    + <%= markdown process.description %> + <% end %> +
    +
    + <% if process.target.present? %> +

    <%= t('legislation.processes.header_full.target') %>

    + <%= markdown process.target %> + <% end %> +
    +
    + <% if process.how_to_participate.present? %> +

    <%= t('legislation.processes.header_full.how_to_participate') %>

    + <%= markdown process.how_to_participate %> + <% end %> +
    + <% if process.additional_info.present? %> +
    +
    + <%= markdown process.additional_info if process.additional_info %> +
    +
    + <% end %> +
    + + <% if process.description.present? || process.target.present? || process.how_to_participate.present? || process.additional_info.present? %> +
    + + + +
    + <% end %> +
    + + <%= render 'legislation/processes/header_full', process: @process, hidden: true %> +<% else %> + <%= render 'legislation/processes/header_full', process: @process, hidden: false %> +<% end %> diff --git a/app/views/legislation/processes/_header_full.html.erb b/app/views/legislation/processes/_header_full.html.erb new file mode 100644 index 000000000..88e2f2861 --- /dev/null +++ b/app/views/legislation/processes/_header_full.html.erb @@ -0,0 +1,48 @@ +
    +
    +
    +

    <%= t('.title') %>

    +

    + <%= process.title %> +

    +
    +
    +
    +
    + +
    +
    + <% if process.description.present? %> +

    <%= t('.description') %>

    + <%= markdown process.description %> + <% end %> +
    +
    + <% if process.target.present? %> +

    <%= t('.target') %>

    + <%= markdown process.target %> + <% end %> +
    +
    + <% if process.how_to_participate.present? %> +

    <%= t('.how_to_participate') %>

    + <%= markdown process.how_to_participate %> + <% end %> +
    + <% if process.additional_info.present? %> +
    +
    + <%= markdown process.additional_info if process.additional_info %> +
    +
    + <% end %> +
    + + <% if process.additional_info.present? %> + + <% end %> +
    diff --git a/app/views/legislation/processes/_key_dates.html.erb b/app/views/legislation/processes/_key_dates.html.erb new file mode 100644 index 000000000..3e5823ae2 --- /dev/null +++ b/app/views/legislation/processes/_key_dates.html.erb @@ -0,0 +1,43 @@ + diff --git a/app/views/legislation/processes/_key_dates_svg.html.erb b/app/views/legislation/processes/_key_dates_svg.html.erb new file mode 100644 index 000000000..c9f2eca94 --- /dev/null +++ b/app/views/legislation/processes/_key_dates_svg.html.erb @@ -0,0 +1,5 @@ + + + + + diff --git a/app/views/legislation/processes/_process.html.erb b/app/views/legislation/processes/_process.html.erb new file mode 100644 index 000000000..9911b28a2 --- /dev/null +++ b/app/views/legislation/processes/_process.html.erb @@ -0,0 +1,44 @@ +
    +
    +
    +
    +

    <%= link_to process.title, process %>

    +
    +
    + +
    + <%= link_to process, class: "button button-legislation big expanded", title: t('.see_latest_comments_title') do %> +   <%= t('.see_latest_comments') %> + <% end %> +
    + +
    + <%= markdown(first_paragraph(process.description)) %> +
    +
    + +
    +
    +

    <%= t('legislation.processes.shared.key_dates') %>

    +
    +
    + +
    +
    +
    <%= t('legislation.processes.shared.debate_dates') %>
    +

    <%= format_date(process.debate_start_date) %> - <%= format_date(process.debate_end_date) %>

    +
    +
    +
    <%= t('legislation.processes.shared.draft_publication_date') %>
    +

    <%= format_date(process.draft_publication_date) %>

    +
    +
    +
    <%= t('legislation.processes.shared.allegations_dates') %>
    +

    <%= format_date(process.allegations_start_date) %> - <%= format_date(process.allegations_end_date) %>

    +
    +
    +
    <%= t('legislation.processes.shared.final_publication_date') %>
    +

    <%= format_date(process.final_publication_date) %>

    +
    +
    +
    diff --git a/app/views/legislation/processes/debate.html.erb b/app/views/legislation/processes/debate.html.erb new file mode 100644 index 000000000..8014f50de --- /dev/null +++ b/app/views/legislation/processes/debate.html.erb @@ -0,0 +1,25 @@ +<% provide :title do %><%= @process.title %><% end %> + +<%= render 'legislation/processes/header', process: @process, header: :full %> + +<%= render 'key_dates', process: @process, phase: :debate %> + +
    +
    +
    +
    +
    + <% if @process.questions.empty? %> +

    <%= t('.empty_questions') %>

    + <% else %> + <%= render @process.questions %> + <% end %> +
    +
    + +
    +
    <%= t('.participate') %>
    +
    +
    +
    +
    diff --git a/app/views/legislation/processes/index.html.erb b/app/views/legislation/processes/index.html.erb new file mode 100644 index 000000000..d9aa8a853 --- /dev/null +++ b/app/views/legislation/processes/index.html.erb @@ -0,0 +1,20 @@ +<% provide :title do %> + <%= t("layouts.header.collaborative_legislation") %> - <%= t("legislation.processes.index.filters.#{@current_filter}") %> +<% end %> + +
    +
    + <%= render 'shared/filter_subnav', i18n_namespace: "legislation.processes.index" %> +
    + +
    +
    + <% if @processes.any? %> + <%= render @processes %> + <%= paginate @processes %> + <% else %> + <%= t(".no_#{@current_filter}_processes") %> + <% end %> +
    +
    +
    diff --git a/app/views/legislation/processes/phase_empty.html.erb b/app/views/legislation/processes/phase_empty.html.erb new file mode 100644 index 000000000..d0534d59c --- /dev/null +++ b/app/views/legislation/processes/phase_empty.html.erb @@ -0,0 +1,13 @@ +<% provide :title do %><%= @process.title %><% end %> + +<%= render 'legislation/processes/header', process: @process, header: :full %> + +<%= render 'legislation/processes/key_dates', process: @process, phase: @phase %> + +
    +
    +
    +

    <%= t(".empty") %>

    +
    +
    +
    diff --git a/app/views/legislation/processes/phase_not_open.html.erb b/app/views/legislation/processes/phase_not_open.html.erb new file mode 100644 index 000000000..141a5fea1 --- /dev/null +++ b/app/views/legislation/processes/phase_not_open.html.erb @@ -0,0 +1,20 @@ +<% provide :title do %><%= @process.title %><% end %> + +<%= render 'legislation/processes/header', process: @process, header: :full %> + +<%= render 'legislation/processes/key_dates', process: @process, phase: @phase %> + +
    +
    +
    +
    +
    +

    <%= t('.not_open') %>

    +
    + +
    +
    +
    +
    +
    +
    diff --git a/app/views/legislation/questions/_answer_form.html.erb b/app/views/legislation/questions/_answer_form.html.erb new file mode 100644 index 000000000..775e93faa --- /dev/null +++ b/app/views/legislation/questions/_answer_form.html.erb @@ -0,0 +1,28 @@ +<% if question.question_options.any? %> + <% if process.open_phase?(:debate) && !answer.persisted? %> + + <%= form_for answer, url: legislation_process_question_answers_path(process, question, answer), remote: true , html: { class: "controls-stacked"} do |f| %> + <% question.question_options.each do |question_option| %> + + <% end %> + <%= f.submit t('legislation.questions.show.answer_question'), class: "button" %> + <% end %> + + <% else %> + +
    + <% question.question_options.each do |question_option| %> + + <% end %> +
    + + <% end %> +<% end %> diff --git a/app/views/legislation/questions/_participation_not_allowed.html.erb b/app/views/legislation/questions/_participation_not_allowed.html.erb new file mode 100644 index 000000000..b186c2f5b --- /dev/null +++ b/app/views/legislation/questions/_participation_not_allowed.html.erb @@ -0,0 +1,24 @@ +<% if user_signed_in? && current_user.organization? %> + +<% elsif user_signed_in? && current_user.unverified? %> + +<% elsif !user_signed_in? %> + +<% elsif !@process.open_phase?(:debate) %> + +<% end %> diff --git a/app/views/legislation/questions/_question.html.erb b/app/views/legislation/questions/_question.html.erb new file mode 100644 index 000000000..750d248c1 --- /dev/null +++ b/app/views/legislation/questions/_question.html.erb @@ -0,0 +1,11 @@ +
    +
    + <%= t('.debate') %> +
    +
    +

    <%= link_to question.title, legislation_process_question_path(question.process, question) %>

    +
    +
    + <%= link_to t('.comments', count: question.comments.count), legislation_process_question_path(question.process, question) %> · <%= format_date question.created_at %> +
    +
    diff --git a/app/views/legislation/questions/show.html.erb b/app/views/legislation/questions/show.html.erb new file mode 100644 index 000000000..ab1443628 --- /dev/null +++ b/app/views/legislation/questions/show.html.erb @@ -0,0 +1,47 @@ +<% provide :title do %><%= @question.title %><% end %> + +
    +
    +
    +
    +

    <%= t('.title') %>

    +

    <%= link_to @process.title, @process %>

    +
    +
    +
    + <% if @question.next_question_id %> + <%= link_to legislation_process_question_path(@process, @question.next_question_id), class: "quiz-next-link" do %> + <%= content_tag :div, class: "quiz-next" do %> + <%= t('.next_question') %> +
    +
    +
    +
    +

    <%= @question.title %>

    +
    + <%= render 'answer_form', process: @process, question: @question, answer: @answer %> + <%= render 'participation_not_allowed' %> +
    +
    + + +
    + + <%= render partial: '/comments/comment_tree', locals: { comment_tree: @comment_tree, comment_flags: @comment_flags } %> + +
    diff --git a/app/views/legislation/shared/_share_buttons.html.erb b/app/views/legislation/shared/_share_buttons.html.erb new file mode 100644 index 000000000..0486b9d2d --- /dev/null +++ b/app/views/legislation/shared/_share_buttons.html.erb @@ -0,0 +1,9 @@ + diff --git a/app/views/legislations/show.html.erb b/app/views/legislations/show.html.erb deleted file mode 100644 index 006d91bb2..000000000 --- a/app/views/legislations/show.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -
    -
    -
    - -   - <%= t("legislation.help.title") %> - - - -
    -
    -
    - -
    -
    -
    -

    <%= @legislation.title %>

    -
    <%= @legislation.body %>
    -
    -
    -
    diff --git a/app/views/pages/accessibility.html.erb b/app/views/pages/accessibility.html.erb index 13c51869b..cd9af91a0 100644 --- a/app/views/pages/accessibility.html.erb +++ b/app/views/pages/accessibility.html.erb @@ -51,6 +51,10 @@ 4 Presupuestos participativos + + 5 + Procesos legislativos +
    diff --git a/app/views/sandbox/_legislation_process_nav.html.erb b/app/views/sandbox/_legislation_process_nav.html.erb new file mode 100644 index 000000000..a99710845 --- /dev/null +++ b/app/views/sandbox/_legislation_process_nav.html.erb @@ -0,0 +1,33 @@ + diff --git a/app/views/sandbox/admin_legislation_debate.html.erb b/app/views/sandbox/admin_legislation_debate.html.erb new file mode 100644 index 000000000..de976de40 --- /dev/null +++ b/app/views/sandbox/admin_legislation_debate.html.erb @@ -0,0 +1,79 @@ +
    +
    + + + Volver + +

    Licencias urbanísticas, declaraciones responsables y comunicaciones previas

    + + + +
    +
    +

    Preguntas asociadas a este proceso

    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    TítuloOpciones de respuestaRespuestasComentarios
    + Esta es mi pregunta + +
      +
    • + No (0) +
    • +
    • + Nunca (0) +
    • +
    +
    0 respuestas0 comentarios
    + Esta es mi pregunta + +
      +
    • + No (0) +
    • +
    • + Nunca (0) +
    • +
    +
    0 respuestas0 comentarios
    +
    +
    diff --git a/app/views/sandbox/admin_legislation_debate_question.html.erb b/app/views/sandbox/admin_legislation_debate_question.html.erb new file mode 100644 index 000000000..f5f6d90d6 --- /dev/null +++ b/app/views/sandbox/admin_legislation_debate_question.html.erb @@ -0,0 +1,103 @@ +
    +
    + + + Volver + + +

    Licencias urbanísticas, declaraciones responsables y comunicaciones previas

    + + + +
    +
    +

    Editar “Esta es mi pregunta”

    +
    +
    + Borrar +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    + + +
    + +
    +
    + +
    +
    +
    + +
    +
    diff --git a/app/views/sandbox/admin_legislation_draft.html.erb b/app/views/sandbox/admin_legislation_draft.html.erb new file mode 100644 index 000000000..363e9fc78 --- /dev/null +++ b/app/views/sandbox/admin_legislation_draft.html.erb @@ -0,0 +1,55 @@ +
    +
    + + + Volver + + +

    Licencias urbanísticas, declaraciones responsables y comunicaciones previas

    + + + +
    +
    +

    Versiones del borrador

    +
    + + +
    + + + + + + + + + + + + + + + + + + + + +
    TítuloCreadoEstadoComentariosVersión final
    + Versión 1 + 2016-12-16draftfalse
    +
    +
    diff --git a/app/views/sandbox/admin_legislation_draft_edit.html.erb b/app/views/sandbox/admin_legislation_draft_edit.html.erb new file mode 100644 index 000000000..3443fdddc --- /dev/null +++ b/app/views/sandbox/admin_legislation_draft_edit.html.erb @@ -0,0 +1,971 @@ +
    +
    + + + Volver + + +

    Licencias urbanísticas, declaraciones responsables y comunicaciones previas

    + + + + +
    +
    + +
    + Ojo, has editado el texto. Para conservar de forma permanente los cambios, no te olvides de hacer click en Guardar. +
    +
    + +
    +

    Versión 1

    +
    +
    + Borrar +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    + +
    +
    + + Usa Markdown para formatear el texto +
    +
    + +
    +
    +
    Consul | Editando Versión 3 del proceso Licencias urbanísticas, declaraciones
    + +
    + + +
    + + Pantalla completa +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    diff --git a/app/views/sandbox/admin_legislation_index.html.erb b/app/views/sandbox/admin_legislation_index.html.erb new file mode 100644 index 000000000..f4365116c --- /dev/null +++ b/app/views/sandbox/admin_legislation_index.html.erb @@ -0,0 +1,52 @@ +
    +
    +
    +

    Procesos de legislación colaborativa

    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    ProcesoComentarios
    + Licencias urbanísticas, declaraciones responsables y comunicaciones previas + + 123 comentarios + + Borrar +
    + Licencias urbanísticas, declaraciones responsables y comunicaciones previas + + 123 comentarios + + Borrar +
    +
    diff --git a/app/views/sandbox/admin_legislation_info.html.erb b/app/views/sandbox/admin_legislation_info.html.erb new file mode 100644 index 000000000..eca094564 --- /dev/null +++ b/app/views/sandbox/admin_legislation_info.html.erb @@ -0,0 +1,157 @@ +
    +
    + + + Volver + +

    Licencias urbanísticas, declaraciones responsables y comunicaciones previas

    + + + +
    + +
    +
    + +
    +
    + Inicio +
    +
    + +
    +
    + Fin +
    +
    + +
    + + +
    + +
    +
    + Inicio +
    +
    + +
    +
    + Fin +
    +
    + +
    + +
    + +
    +
    + Inicio +
    +
    + +
    +
    + Fin +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + Usa Markdown para formatear el texto +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    diff --git a/app/views/sandbox/admin_test_sandbox.html.erb b/app/views/sandbox/admin_test_sandbox.html.erb new file mode 100644 index 000000000..5d61aab9d --- /dev/null +++ b/app/views/sandbox/admin_test_sandbox.html.erb @@ -0,0 +1,61 @@ + diff --git a/app/views/sandbox/index.html.erb b/app/views/sandbox/index.html.erb new file mode 100644 index 000000000..9d6303698 --- /dev/null +++ b/app/views/sandbox/index.html.erb @@ -0,0 +1,13 @@ +
    +
    +
    +

    Welcome to sandbox

    + +
      + <% @templates.each do |template| %> +
    • <%= link_to template, "/sandbox/" + template %>
    • + <% end %> +
    +
    +
    +
    diff --git a/app/views/sandbox/legislation_allegations.html.erb b/app/views/sandbox/legislation_allegations.html.erb new file mode 100644 index 000000000..60815cbd6 --- /dev/null +++ b/app/views/sandbox/legislation_allegations.html.erb @@ -0,0 +1,465 @@ +
    +
    +
    +

    + Licencias urbanísticas, declaraciones responsables y comunicaciones previas +

    +
    + +
    + +
    +
    +

    En qué consiste

    +

    Se va a modificar la regulación del procedimiento para la autorización de obras y la apertura de locales comerciales o empresariales para simplificar y agilizar trámites.

    +
    +
    +

    A quién va dirigido

    +
      +
    • Ciudadanos con vivienda en propiedad
    • +
    • Profesionales de la construcción y reformas
    • +
    • Empresarios con locales comerciales
    • +
    +
    +
    +

    Cómo puedes participar

    +
      +
    • Participa en el debate previo para identificar los problemas a solucionar, la necesidad de esta normativa, sus objetivos y posibles soluciones alternativas.
    • +
    • Después del debate el Ayuntamiento presentará un borrador del texto al cual podrás realizar comentarios y alegaciones.
    • +
    +
    +
    +
    +

    En qué consiste

    +

    El ayuntamiento de Madrid somete...

    +
    +
    +
    + +
    + + + +
    +
    + +
    + <%= render 'sandbox/legislation_process_nav' %> + +
    +
    +
    +

    Estás viendo la revisión

    +
    + + ver resumen de cambios +
    + actualizada el 13 nov 2017 +
    + +
    + +
    + +
    +
    +
    Texto
    +
    +
    +

    TÍTULO PRELIMINAR. Objeto y definiciones

    +

    Artículo 1.

    +
    +

    1.Esta Ordenanza tiene por objeto establecer las condiciones generales que deben cumplir los distintos elementos integrados en el denominado mobiliario urbano, tanto en lo que se refiere a su emplazamiento como a las características propias de dichos elementos.

    +
    +

    2.Para determinar las condiciones relativas a la explotación del mobiliario urbano y a los requisitos que, en su caso, hayan de reunir los respectivos titulares, se estará a la normativa específica establecida para los distintos elementos y a las señaladas en el título que autorice su instalación y funcionamiento.

    +

    3.El mobiliario urbano deberá cumplir los criterios estéticos y de diseño para las distintas zonas de Madrid, aprobados por el titular del Área de Gobierno competente en materia de mobiliario urbano. En aquellos casos en los que la normativa aplicable así lo exija, deberá obtenerse dictamen de la comisión competente en materia de protección del patrimonio histórico y artístico.

    +

    (redacción dada por la Ordenanza de 30 de marzo 2011 de Adaptación al ámbito de la Ciudad de Madrid de las previsiones contenidas en la normativa estatal y autonómica de transposición de la Directiva 2006/123/CE)

    + +

    Artículo 2.

    +

    1. A los efectos de esta Ordenanza se entiende por mobiliario urbano el conjunto de instalaciones o elementos que ocupan un espacio público, y cuya finalidad sea la de atender una necesidad social o prestar un determinado servicio al vecindario.

    +
    +

    2.En el concepto indicado estarán incluidas tanto las instalaciones y elementos de titularidad pública, explotados directamente o por concesión: bancos, bolardos, marquesinas, papeleras, etcétera, como los colocados por particulares, previa autorización municipal, tales como quioscos o puestos fijos, de temporada u ocasionales.

    +
    +

    (apartado 2 del art. 2, redacción dada por la Ordenanza de 30 de marzo 2011 de Adaptación al ámbito de la Ciudad de Madrid de las previsiones contenidas en la normativa estatal y autonómica de transposición de la Directiva 2006/123/ CE)

    + +

    Artículo 3.

    +

    Constituirá criterio general para la implantación de mobiliario urbano la armonización de las finalidades asignadas al mismo con las funciones generales de los espacios públicos, la coordinación de los distintos elementos procurando, cuando fuera posible, la polivalencia de cada uno de ellos para evitar la ocupación intensiva de aquellos espacios y la adecuación, tanto por su emplazamiento como por su diseño, al entono urbano en que se localicen.

    +

    TÍTULO I. DE LOS EMPLAZAMIENTOS DE MOBILIARIO URBANO

    + +

    Artículo 4.

    +

    Cuando se trate de mobiliario urbano de titularidad pública, el número, localización y características de sus emplazamientos estará determinado en el correspondiente acuerdo de implantación o bases de concesión, si fuera municipal, y en el de autorización, si fuese promovido por otras entidades.

    + +

    Artículo 5.

    + +

    Los interesados tendrán reconocidos específicamente, además de los establecidos con carácter general en otras normas, los siguientes derechos: +

    1. Iniciar inmediatamente el ejercicio de la actividad cuando esté sometida a declaración responsable, o una vez obtenida la licencia de primera ocupación y funcionamiento.

    +

    2. Obtener la licencia de actividad y funcionamiento en el plazo establecido en cada caso, sin perjuicio de los efectos establecidos en la legislación aplicable en el ámbito del silencio administrativo.

    +

    3. No presentar documentación que obre en poder de los servicios municipales

    +

    4. Presentar quejas, reclamaciones y sugerencias sobre el funcionamiento de los servicios municipales y de las entidades colaboradoras urbanísticas.

    + +

    Artículo 6.

    +
    +

    1. Durante la realización de las obras o la implantación de la actividad y previamente al acto de comprobación, no se considerarán modificación de la licencia o de la declaración responsable las alteraciones que se hayan producido en el edificio, local o sus instalaciones cuando las mismas se ajusten a la normativa que las regula, salvo que afecten a las condiciones de volumen y forma de los edificios, a la posición y ocupación del edificio en la parcela, a la edificabilidad o superficie del local, al número de locales. Si no se ajustasen se aplicarán los mecanismos de restablecimiento de la legalidad urbanística.

    +
    + +

    Se considerará modificación el cambio de actividad, salvo que la nueva actividad o la inicial con la incorporación de alguna complementaria de ella, tenga la misma consideración y exigencias urbanísticas, ambientales, de seguridad y salubridad. Las variaciones producidas se relacionarán en el acta de comprobación, sin necesidad de tramitar licencia o declaración aparte, quedando legalizadas con la concesión de la licencia de primera ocupación y funcionamiento o el acto de comprobación posterior. Cuando las variaciones se hayan concretado en obras que requieran proyecto de obras de edificación de acuerdo con el artículo 2.2 de la Ley de Ordenación de la Edificación se incorporará el correspondiente proyecto modificado.

    +

    2. Durante el ejercicio de las actividades con licencia o declaración responsable, y con las mismas salvedades indicadas en el punto 1, no se considerará modificación de la licencia o declaración las variaciones que se hayan producido en la actividad, el local o sus instalaciones cuando no alteren las condiciones de repercusión ambiental, seguridad o salubridad por debajo de las exigencias técnicas establecidas para las mismas por la normativa vigente. Tampoco se considerará modificación el cambio de actividad o la incorporación de alguna complementaria a la misma cuando esta tenga la misma consideración y exigencias urbanísticas, ambientales, de seguridad y salubridad que la primera.

    +

    Las obras que han dado lugar a estas variaciones se legalizarán a través de licencia o declaración responsable, dependiendo de la entidad de las mismas. La licencia que se conceda o la declaración se limitarán a recoger el contenido de la modificación, haciendo referencia a la licencia del establecimiento.

    +

    3. En el caso de actividades de espectáculos públicos y recreativas también se considerará modificación de licenciao declaración responsable el cambio de actividad de las indicadas en el Catálogo, así como el incremento del aforo.

    +

    4. Cuando la modificación sea requerida de oficio, el requerimiento indicará las alteraciones existentes, motivando la necesidad de la modificación de la licencia o de la declaración responsable.

    +

    5. Durante la ejecución de las obras autorizadas no precisarán modificación de licencia las variaciones en el número de plazas de aparcamiento que no supongan disminución de la dotación obligatoria de servicio del edificio, sin perjuicio de su constancia documentada en el expediente.

    +
    +
    + +
    +
    +
    + Comentarios +
    +
    + +
    + Comentarios +
    + +
    +
    + +
    34 comentarios
    + +
    +
    +
    +
    +

    Mi comentario va encaminado a que no nos equivoquemos al pensar que esta es una opci...

    +
    +
    +
    + + +
    +
    +
    Sin votos
    + + +
    +
    +
    + +
    +
    +

    Mi comentario va encaminado a que no nos equivoquemos al pensar que esta es una opci...

    +
    +
    +
    + + +
    +
    +
    Sin votos
    + + +
    +
    +
    + +
    +
    +

    Mi comentario va encaminado a que no nos equivoquemos al pensar que esta es una opci...

    +
    +
    +
    + + +
    +
    +
    Sin votos
    + + +
    +
    +
    +
    + +
    + +
    +
    + +
    34 comentarios
    + +
    +
    +
    +
    +

    Mi comentario va encaminado a que no nos equivoquemos al pensar que esta es una opci...

    +
    +
    +
    + + +
    +
    +
    Sin votos
    + + +
    +
    +
    + +
    +
    +

    Mi comentario va encaminado a que no nos equivoquemos al pensar que esta es una opci...

    +
    +
    +
    + + +
    +
    +
    Sin votos
    + + +
    +
    +
    + +
    +
    +

    Mi comentario va encaminado a que no nos equivoquemos al pensar que esta es una opci...

    +
    +
    +
    + + +
    +
    +
    Sin votos
    + + +
    +
    +
    +
    + + + +
    + +
    +
    + +
    34 comentarios
    + +
    +
    +
    +
    +

    En la actualidad, el número de autobuses de la EMT suman 1.900 de los cuales 740 funcionan con gas natural (el 42%), con los 170 que se van a adquirir en 2016, el cómputo sube a 900, lo que significa casi el 50% del total. Con tecnología híbrida existen 23 y se prevé comprar 30 más, lo que supone 53 vehículos, y hay 20 minibuses eléctricos puros. Por último, el 80% de los autobuses de diésel están catalizados a niveles euro4 y euro5 (470), “con lo que podríamos asegurar que el 78% de nuestra flota es verde.

    +

    En la actualidad, el número de autobuses de la EMT suman 1.900 de los cuales 740 funcionan con gas natural (el 42%), con los 170 que se van a adquirir en 2016, el cómputo sube a 900, lo que significa casi el 50% del total. Con tecnología híbrida existen 23 y se prevé comprar 30 más, lo que supone 53 vehículos, y hay 20 minibuses eléctricos puros. Por último, el 80% de los autobuses de diésel están catalizados a niveles euro4 y euro5 (470), “con lo que podríamos asegurar que el 78% de nuestra flota es verde.

    +
    +
    +
    + +
    +
    +
    Sin votos
    + + +
    +
    +
    + +
    +
    +

    Mi comentario va encaminado a que no nos equivoquemos al pensar que esta es una opci...

    +
    +
    +
    + + +
    +
    +
    Sin votos
    + + +
    +
    +
    + +
    +
    +

    Mi comentario va encaminado a que no nos equivoquemos al pensar que esta es una opci...

    +
    +
    +
    + + +
    +
    +
    Sin votos
    + + +
    +
    +
    +
    + +
    + +
    + +
    +
    +
    diff --git a/app/views/sandbox/legislation_debate.html.erb b/app/views/sandbox/legislation_debate.html.erb new file mode 100644 index 000000000..485d37c1d --- /dev/null +++ b/app/views/sandbox/legislation_debate.html.erb @@ -0,0 +1,146 @@ +
    +
    +
    +

    Colabora en la elaboración de la normativa sobre

    +

    + Licencias urbanísticas, declaraciones responsables y comunicaciones previas +

    +
    + +
    + +
    +
    +

    En qué consiste

    +

    Se va a modificar la regulación del procedimiento para la autorización de obras y la apertura de locales comerciales o empresariales para simplificar y agilizar trámites.

    +
    +
    +

    A quién va dirigido

    +
      +
    • Ciudadanos con vivienda en propiedad
    • +
    • Profesionales de la construcción y reformas
    • +
    • Empresarios con locales comerciales
    • +
    +
    +
    +

    Cómo puedes participar

    +
      +
    • Participa en el debate previo para identificar los problemas a solucionar, la necesidad de esta normativa, sus objetivos y posibles soluciones alternativas.
    • +
    • Después del debate el Ayuntamiento presentará un borrador del texto al cual podrás realizar comentarios y alegaciones.
    • +
    +
    +
    +
    +

    En qué consiste

    +

    1.Esta Ordenanza tiene por objeto establecer las condiciones generales que deben cumplir los distintos elementos integrados en el denominado mobiliario urbano, tanto en lo que se refiere a su emplazamiento como a las características propias de dichos elementos.

    + +

    2.Para determinar las condiciones relativas a la explotación del mobiliario urbano y a los requisitos que, en su caso, hayan de reunir los respectivos titulares, se estará a la normativa específica establecida para los distintos elementos y a las señaladas en el título que autorice su instalación y funcionamiento.

    + +

    Preámbulo

    +

    3.El mobiliario urbano deberá cumplir los criterios estéticos y de diseño para las distintas zonas de Madrid, aprobados por el titular del Área de Gobierno competente en materia de mobiliario urbano. En aquellos casos en los que la normativa aplicable así lo exija, deberá obtenerse dictamen de la comisión competente en materia de protección del patrimonio histórico y artístico.

    + +

    (redacción dada por la Ordenanza de 30 de marzo 2011 de Adaptación al ámbito de la Ciudad de Madrid de las previsiones contenidas en la normativa estatal y autonómica de transposición de la Directiva 2006/123/CE)

    +
    +
    +
    + + +
    + +
    + <%= render 'sandbox/legislation_process_nav' %> + +
    +
    +
    +
    + + + + + + + + + + + +
    +
    + +
    +
    Realiza tus aportaciones al debate previo participando en los siguientes temas.
    +
    + +
    +
    +
    diff --git a/app/views/sandbox/legislation_debate_quiz.html.erb b/app/views/sandbox/legislation_debate_quiz.html.erb new file mode 100644 index 000000000..40f691094 --- /dev/null +++ b/app/views/sandbox/legislation_debate_quiz.html.erb @@ -0,0 +1,57 @@ +
    + +
    +
    +

    ¿Considera necesario realizar una nueva regulación para facilitar y simplificar las obras en viviendas, o la modificación y apertura de locales comerciales o empresariales?

    +
    +
    + + + +
    +
    +
    + +
    +
    + + +
    + +
    +
    + COMENTARIOS +
    +
    diff --git a/app/views/sandbox/legislation_draft.html.erb b/app/views/sandbox/legislation_draft.html.erb new file mode 100644 index 000000000..78848f277 --- /dev/null +++ b/app/views/sandbox/legislation_draft.html.erb @@ -0,0 +1,74 @@ +
    +
    +
    +

    Colabora en la elaboración de la normativa sobre

    +

    + Licencias urbanísticas, declaraciones responsables y comunicaciones previas +

    +
    + +
    + +
    +
    +

    En qué consiste

    +

    Se va a modificar la regulación del procedimiento para la autorización de obras y la apertura de locales comerciales o empresariales para simplificar y agilizar trámites.

    +
    +
    +

    A quién va dirigido

    +
      +
    • Ciudadanos con vivienda en propiedad
    • +
    • Profesionales de la construcción y reformas
    • +
    • Empresarios con locales comerciales
    • +
    +
    +
    +

    Cómo puedes participar

    +
      +
    • Participa en el debate previo para identificar los problemas a solucionar, la necesidad de esta normativa, sus objetivos y posibles soluciones alternativas.
    • +
    • Después del debate el Ayuntamiento presentará un borrador del texto al cual podrás realizar comentarios y alegaciones.
    • +
    +
    +
    +
    +

    En qué consiste

    +

    El ayuntamiento de Madrid somete...

    +
    +
    +
    + + +
    + +
    + <%= render 'sandbox/legislation_process_nav' %> + +
    +
    +
    + +
    +

    Esta fase del proceso todavía no está abierta

    +

    Suscríbete al proceso para recibir un aviso en el momento en que se abra.

    +
    + +
    + +
    + +
    +
    +
    +
    diff --git a/app/views/sandbox/legislation_draft_changes.html.erb b/app/views/sandbox/legislation_draft_changes.html.erb new file mode 100644 index 000000000..122d14b74 --- /dev/null +++ b/app/views/sandbox/legislation_draft_changes.html.erb @@ -0,0 +1,58 @@ +
    +
    +
    +

    + Licencias urbanísticas, declaraciones responsables y comunicaciones previas +

    +
    + +
    + +
    + + + +
    +
    + +
    + <%= render 'sandbox/legislation_process_nav' %> + +
    +
    +
    +

    Estás viendo la revisión

    +
    + +
    + actualizada el 13 nov 2017 +
    + +
    + +
    +
    +

    El 12 nov 2016 se publica un nuevo borrador de la normativa sobre Licencias urbanísticas, que incorpora las aportaciones de la ciudadanía realizadas a través de la web de decide.madrid.es. A continuación se detallan los principales cambios que se han introducido en el texto:

    + +
      +
    • Se detalla el ámbito de actuación ver apartado
    • +
    • Se reescribe la introducción al Capítulo I para incorporar sugerencias ciudadanas ver apartado
    • +
    • Se detalla el ámbito de actuación ver apartado
    • +
    • Se reescribe la introducción al Capítulo I para incorporar sugerencias ciudadanas ver apartado
    • +
    • Se detalla el ámbito de actuación ver apartado
    • +
    • Se reescribe la introducción al Capítulo I para incorporar sugerencias ciudadanas ver apartado
    • +
    +
    +
    +
    diff --git a/app/views/sandbox/legislation_draft_comment.html.erb b/app/views/sandbox/legislation_draft_comment.html.erb new file mode 100644 index 000000000..0d9191e71 --- /dev/null +++ b/app/views/sandbox/legislation_draft_comment.html.erb @@ -0,0 +1,68 @@ +
    +
    +
    +

    + Licencias urbanísticas, declaraciones responsables y comunicaciones previas +

    +
    + +
    + +
    + + + +
    +
    + +
    + <%= render 'sandbox/legislation_process_nav' %> + +
    +
    +
    +

    Comentarios de la versión

    +
    + +
    + actualizada el 13 nov 2017 +
    + +
    + +
    +
    +
    + Comentarios sobre +
    +
    +
    + 1. Esta ordenanza tiene por objeto regular los medios de intervención administrativa para el control de la legalidad de las actuaciones urbanísticas. +
    + +
    +
    + +
    +
    + COMENTARIOS +
    +
    + +
    +
    +
    +
    diff --git a/app/views/sandbox/legislation_draft_comments.html.erb b/app/views/sandbox/legislation_draft_comments.html.erb new file mode 100644 index 000000000..c9ba64555 --- /dev/null +++ b/app/views/sandbox/legislation_draft_comments.html.erb @@ -0,0 +1,83 @@ +
    +
    +
    +

    + Licencias urbanísticas, declaraciones responsables y comunicaciones previas +

    +
    + +
    + +
    + + + +
    +
    + +
    + <%= render 'sandbox/legislation_process_nav' %> + +
    +
    +
    +

    Comentarios de la versión

    +
    + +
    + actualizada el 13 nov 2017 +
    + +
    + +
    +
    +
    + Comentarios sobre + Ver en contexto +
    + 1. Esta ordenanza tiene por objeto regular los medios de intervención administrativa para el control de la legalidad de las actuaciones urbanísticas. +
    + 34 comentarios +
    + +
    + Comentarios sobre + Ver en contexto +
    + 1. Esta ordenanza tiene por objeto regular los medios de intervención administrativa para el control de la legalidad de las actuaciones urbanísticas. +
    + 34 comentarios +
    + +
    + Comentarios sobre + Ver en contexto +
    + 1. Esta ordenanza tiene por objeto regular los medios de intervención administrativa para el control de la legalidad de las actuaciones urbanísticas. +
    + 34 comentarios +
    + +
    + Comentarios sobre + Ver en contexto +
    + 1. Esta ordenanza tiene por objeto regular los medios de intervención administrativa para el control de la legalidad de las actuaciones urbanísticas. +
    + 34 comentarios +
    +
    +
    +
    diff --git a/app/views/sandbox/legislation_home.html.erb b/app/views/sandbox/legislation_home.html.erb new file mode 100644 index 000000000..c054788b7 --- /dev/null +++ b/app/views/sandbox/legislation_home.html.erb @@ -0,0 +1,67 @@ +
    +
    +
    +

    + PROCESOS DESTACADOS +

    +
    +
    +
    + +
    +
    +
      +
    • Procesos activos

    • +
    • Próximamente

    • +
    • Terminados

    • +
    +
    + +
    +
    + +
    +
    +
    +
    +

    Licencias urbanísticas, declaraciones responsables y comunicaciones previas

    +

    Se va a modificar la regulación del procedimiento para la autorización de obras y la apertura de locales comerciales o empresariales para simplificar y agilizar trámites

    +
    +
    + + +
    + +
    +
    +

    Fechas clave:

    +
    +
    + +
    +
    +
    Debate previo
    +

    15 nov 2016 - 15 dic 2016

    +
    +
    +
    Publicación borrador
    +

    1 dic 2016

    +
    +
    +
    Alegaciones
    +

    1 dic 2016 - 15 dic 2016

    +
    +
    +
    Publicación resultados
    +

    15 feb 2017

    +
    +
    +
    + +
    +
    +
    diff --git a/app/views/shared/_subnavigation.html.erb b/app/views/shared/_subnavigation.html.erb index 991d771ce..b6e7bf096 100644 --- a/app/views/shared/_subnavigation.html.erb +++ b/app/views/shared/_subnavigation.html.erb @@ -22,6 +22,14 @@ accesskey: "3" %>
  • <% end %> + <% if feature?(:legislation) %> +
  • + <%= layout_menu_link_to t("layouts.header.collaborative_legislation"), + legislation_processes_path, + controller.class.parent == Legislation, + accesskey: "l" %> +
  • + <% end %> <% if feature?(:spending_proposals) %>
  • <%= layout_menu_link_to t("layouts.header.spending_proposals"), diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index ca6eae46e..56cc52a78 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -36,6 +36,7 @@ data: - config/locales/kaminari.%{locale}.yml - config/locales/officing.%{locale}.yml - config/locales/budgets.%{locale}.yml + - config/locales/legislation.%{locale}.yml # Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom: # `i18n-tasks normalize -p` will force move the keys according to these rules @@ -127,6 +128,10 @@ ignore_unused: - 'admin.organizations.index.filter*' - 'admin.users.index.filter*' - 'admin.activity.show.filter*' + - 'admin.legislation.processes.index.filter*' + - 'admin.legislation.processes.*.submit_button' + - 'admin.legislation.draft_versions.*.submit_button' + - 'admin.legislation.questions.*.submit_button' - 'admin.comments.index.hidden_*' - 'admin.settings.index.features.*' - 'admin.polls.*.submit_button' @@ -152,11 +157,14 @@ ignore_unused: - 'notifications.index.comments_on*' - 'notifications.index.replies_to*' - 'notifications.index.proposal_notification*' + - 'legislation.processes.index.filter*' - 'helpers.page_entries_info.*' # kaminari - 'views.pagination.*' # kaminari - 'shared.suggest.*' - 'invisible_captcha.*' - 'admin.site_customization.pages.page.status_*' + - 'admin.legislation.processes.process.*' + - 'legislation.processes.index.*' # - '{devise,kaminari,will_paginate}.*' # - 'simple_form.{yes,no}' # - 'simple_form.{placeholders,hints,labels}.*' diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 608803579..d46e7af84 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -55,6 +55,21 @@ en: site_customization/content_block: one: Custom content block other: Custom content blocks + legislation/process: + one: "Process" + other: "Processes" + legislation/draft_versions: + one: "Draft version" + other: "Draft versions" + legislation/questions: + one: "Question" + other: "Questions" + legislation/question_options: + one: "Question option" + other: "Question options" + legislation/answers: + one: "Answer" + other: "Answers" attributes: budget: name: "Name" @@ -146,6 +161,33 @@ en: name: Name locale: locale body: Body + legislation/process: + title: Process Title + description: Description + target: Target + how_to_participate: How to participate + additional_info: Additional info + start_date: Start date + end_date: End date + debate_start_date: Debate start date + debate_end_date: Debate end date + draft_publication_date: Draft publication date + allegations_start_date: Allegations start date + allegations_end_date: Allegations end date + final_publication_date: Final result publication date + legislation/draft_version: + title: Version title + body: Text + changelog: Changes + status: Status + final_version: Final version + legislation/question: + title: Title + question_options: Options + legislation/question_option: + value: Value + legislation/annotation: + text: Comment errors: models: user: @@ -165,6 +207,14 @@ en: document_number: not_in_census: "Document not in census" has_voted: "User has already voted" + legislation/process: + attributes: + end_date: + invalid_date_range: must be on or after the start date + debate_end_date: + invalid_date_range: must be on or after the debate start date + allegations_end_date: + invalid_date_range: must be on or after the allegations start date proposal: attributes: tag_list: diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index 71e166026..f927740b9 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -55,6 +55,21 @@ es: site_customization/content_block: one: Bloque other: Bloques + legislation/process: + one: "Proceso" + other: "Procesos" + legislation/draft_texts: + one: "Borrador" + other: "Borradores" + legislation/questions: + one: "Pregunta" + other: "Preguntas" + legislation/question_options: + one: "Opción de respuesta cerrada" + other: "Opciones de respuesta cerrada" + legislation/answers: + one: "Respuesta" + other: "Respuestas" attributes: budget: name: "Nombre" @@ -141,6 +156,33 @@ es: name: Nombre locale: Idioma body: Contenido + legislation/process: + title: Título del proceso + description: En qué consiste + target: A quién afecta + how_to_participate: Cómo participar + additional_info: Información adicional + start_date: Fecha de inicio del proceso + end_date: Fecha de fin del proceso + debate_start_date: Fecha de inicio del debate + debate_end_date: Fecha de fin del debate + draft_publication_date: Fecha de publicación del borrador + allegations_start_date: Fecha de inicio de alegaciones + allegations_end_date: Fecha de fin de alegaciones + final_publication_date: Fecha de publicación del resultado final + legislation/draft_version: + title: Título de la version + body: Texto + changelog: Cambios + status: Estado + final_version: Versión final + legislation/question: + title: Título + question_options: Respuestas + legislation/question_option: + value: Valor + legislation/annotation: + text: Comentario errors: models: user: @@ -160,6 +202,14 @@ es: document_number: not_in_census: "Este documento no aparece en el censo" has_voted: "Este usuario ya ha votado" + legislation/process: + attributes: + end_date: + invalid_date_range: tiene que ser igual o posterior a la fecha de inicio + debate_end_date: + invalid_date_range: tiene que ser igual o posterior a la fecha de inicio del debate + allegations_end_date: + invalid_date_range: tiene que ser igual o posterior a la fecha de inicio de las alegaciones proposal: attributes: tag_list: diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 1d1bf9cb0..04efd6464 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -181,6 +181,143 @@ en: with_confirmed_hide: Confirmed without_confirmed_hide: Pending title: Hidden debates + legislation: + processes: + create: + notice: 'Process created successfully. Click to visit' + error: Process couldn't be created + update: + notice: 'Process updated successfully. Click to visit' + error: Process couldn't be updated + destroy: + notice: Process deleted successfully + edit: + back: Back + submit_button: Save changes + errors: + form: + error: Error + form: + active: Active + process: Process + debate_phase: Debate phase + allegations_phase: Allegations phase + start: Start + end: End + use_markdown: Use Markdown to format the text + title_placeholder: The title of the process + description_placeholder: Add a description of the process + target_placeholder: Describe who is the target of the process + how_to_participate_placeholder: Describe how to participate + additional_info_placeholder: Add an additional information you consider useful + index: + create: New process + delete: Delete + title: Legislation processess + filters: + open: Open + next: Next + past: Past + all: All + new: + back: Back + title: Create new collaborative legislation process + submit_button: Create process + process: + title: Process + comments: Comments + status: Status + creation_date: Creation date + status_open: Open + status_closed: Closed + status_planned: Planned + subnav: + info: Information + draft_texts: Text + questions: Debate + draft_versions: + create: + notice: 'Draft created successfully. Click to visit' + error: Draft couldn't be created + update: + notice: 'Draft updated successfully. Click to visit' + error: Draft couldn't be updated + destroy: + notice: Draft deleted successfully + edit: + back: Back + submit_button: Save changes + warning: You've edited the text, don't forget to click on Save to permanently save the changes. + errors: + form: + error: Error + form: + title_html: 'Editing %{draft_version_title} from the process %{process_title}' + launch_text_editor: Launch text editor + close_text_editor: Close text editor + use_markdown: Use Markdown to format the text + hints: + final_version: This version will be published as Final Result for this process. Comments won't be allowed in this version. + status: + draft: You can preview as admin, no one else can see it + published: Visible for everybody + title_placeholder: Write the title of the draft version + changelog_placeholder: Add the main changes from the previous version + body_placeholder: Write down the draft text + index: + title: Draft versions + create: Create version + delete: Delete + preview: Preview + new: + back: Back + title: Create new version + submit_button: Create version + statuses: + draft: Draft + published: Published + table: + title: Title + created_at: Created at + comments: Comments + final_version: Final version + status: Status + questions: + create: + notice: 'Question created successfully. Click to visit' + error: Question couldn't be created + update: + notice: 'Question updated successfully. Click to visit' + error: Question couldn't be updated + destroy: + notice: Question deleted successfully + edit: + back: Back + title: "Edit “%{question_title}”" + submit_button: Save changes + errors: + form: + error: Error + form: + add_option: Add option + title_placeholder: Add a title + value_placeholder: Add a closed answer + index: + back: Back + title: Questions associated to this process + create: Create question + delete: Delete + new: + back: Back + title: Create new question + submit_button: Create question + table: + title: Title + question_options: Question options + answers_count: Answers count + comments_count: Comments count + question_option_fields: + remove_option: Remove option managers: index: title: Managers @@ -226,6 +363,7 @@ en: title_profiles: Profiles title_banners: Banners title_site_customization: Site customization + legislation: Collaborative Legislation moderators: index: title: Moderators diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index b42843d70..bf2300d36 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -181,6 +181,143 @@ es: with_confirmed_hide: Confirmados without_confirmed_hide: Pendientes title: Debates ocultos + legislation: + processes: + create: + notice: 'Proceso creado correctamente. Haz click para verlo' + error: No se ha podido crear el proceso + update: + notice: 'Proceso actualizado correctamente. Haz click para verlo' + error: No se ha podido actualizar el proceso + destroy: + notice: Proceso eliminado correctamente + edit: + back: Volver + submit_button: Guardar cambios + errors: + form: + error: Error + form: + active: Activa + process: Proceso + debate_phase: Fase previa + allegations_phase: Fase de alegaciones + start: Inicio + end: Fin + use_markdown: Usa Markdown para formatear el texto + title_placeholder: Escribe el título del proceso + description_placeholder: Añade una descripción del proceso + target_placeholder: Describe a quién va dirigido + how_to_participate_placeholder: Describe cómo participar + additional_info_placeholder: Añade cualquier información adicional que pueda ser de interés + index: + create: Nuevo proceso + delete: Borrar + title: Procesos de legislación colaborativa + filters: + open: Abiertos + next: Próximamente + past: Pasados + all: Todos + new: + back: Volver + title: Crear nuevo proceso de legislación colaborativa + submit_button: Crear proceso + process: + title: Proceso + comments: Comentarios + status: Estado + creation_date: Fecha creación + status_open: Abierto + status_closed: Cerrado + status_planned: Próximamente + subnav: + info: Información + draft_texts: Texto + questions: Debate + draft_versions: + create: + notice: 'Borrador creado correctamente. Haz click para verlo' + error: No se ha podido crear el borrador + update: + notice: 'Borrador actualizado correctamente. Haz click para verlo' + error: No se ha podido actualizar el borrador + destroy: + notice: Borrador eliminado correctamente + edit: + back: Volver + submit_button: Guardar cambios + warning: Ojo, has editado el texto. Para conservar de forma permanente los cambios, no te olvides de hacer click en Guardar. + errors: + form: + error: Error + form: + title_html: 'Editando %{draft_version_title} del proceso %{process_title}' + launch_text_editor: Lanzar editor de texto + close_text_editor: Cerrar editor de texto + use_markdown: Usa Markdown para formatear el texto + hints: + final_version: Será la versión que se publique en Publicación de Resultados. Esta versión no se podrá comentar + status: + draft: Podrás previsualizarlo como logueado, nadie más lo podrá ver + published: Será visible para todo el mundo + title_placeholder: Escribe el título de esta versión del borrador + changelog_placeholder: Describe cualquier cambio relevante con la versión anterior + body_placeholder: Escribe el texto del borrador + index: + title: Versiones del borrador + create: Crear versión + delete: Borrar + preview: Previsualizar + new: + back: Volver + title: Crear nueva versión + submit_button: Crear versión + statuses: + draft: Borrador + published: Publicado + table: + title: Título + created_at: Creado + comments: Comentarios + final_version: Versión final + status: Estado + questions: + create: + notice: 'Pregunta creada correctamente. Haz click para verla' + error: No se ha podido crear la pregunta + update: + notice: 'Pregunta actualizada correctamente. Haz click para verla' + error: No se ha podido actualizar la pregunta + destroy: + notice: Pregunta eliminada correctamente + edit: + back: Volver + title: "Editar “%{question_title}”" + submit_button: Guardar cambios + errors: + form: + error: Error + form: + add_option: +Añadir respuesta cerrada + title_placeholder: Escribe un título a la pregunta + value_placeholder: Escribe una respuesta cerrada + index: + back: Volver + title: Preguntas asociadas a este proceso + create: Crear pregunta + delete: Borrar + new: + back: Volver + title: Crear nueva pregunta + submit_button: Crear pregunta + table: + title: Título + question_options: Opciones de respuesta + answers_count: Número de respuestas + comments_count: Número de comentarios + question_option_fields: + remove_option: Eliminar managers: index: title: Gestores @@ -226,6 +363,7 @@ es: title_profiles: Perfiles title_banners: Banners title_site_customization: Personalizar sitio + legislation: Legislación colaborativa moderators: index: title: Moderadores diff --git a/config/locales/en.yml b/config/locales/en.yml index 56b8c6a98..1b1f9fb5a 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -34,6 +34,9 @@ en: close: Close menu: Menu comments: + comments_closed: Comments are closed + verified_only: To participate %{verify_account} + verify_account: verify your account comment: admin: Administrator author: Author @@ -62,6 +65,7 @@ en: comments_helper: comment_button: Publish comment comment_link: Comment + comments_title: Comments reply_button: Publish reply reply_link: Reply debates: @@ -188,6 +192,7 @@ en: header: administration: Administration available_locales: Available languages + collaborative_legislation: Legislation processes debates: Debates external_link_blog: Blog external_link_opendata: Open data @@ -217,7 +222,9 @@ en: see_all: See proposals budgets: Participatory budgeting spending_proposals: Spending Proposals - legislation: + admin: + watch_form_message: 'You have unsaved changes. Do you confirm to leave the page?' + legacy_legislation: help: alt: Select the text you want to comment and press the button with the pencil. text: To comment this document you must %{sign_in} or %{sign_up}. Then select the text you want to comment and press the button with the pencil. @@ -688,13 +695,6 @@ en: user_permission_verify_info: "* Only for users on Census." user_permission_verify_my_account: Verify my account user_permission_votes: Participate on final voting - legislation: - help: - title: "How I can comment this document?" - text: "To comment this document you must %{sign_in} or %{sign_up}. Then select the text you want to comment and press the button with the pencil." - text_sign_in: "login" - text_sign_up: "sign up" - alt: "Select the text you want to comment and press the button with the pencil." invisible_captcha: sentence_for_humans: "If you are human, ignore this field" timestamp_error_message: "Sorry, that was too quick! Please resubmit." diff --git a/config/locales/es.yml b/config/locales/es.yml index e0f2ba001..b4f47d713 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -34,6 +34,9 @@ es: close: Cerrar menu: Menú comments: + comments_closed: Los comentarios están cerrados + verified_only: Para participar %{verify_account} + verify_account: verifica tu cuenta comment: admin: Administrador author: Autor @@ -62,6 +65,7 @@ es: comments_helper: comment_button: Publicar comentario comment_link: Comentar + comments_title: Comentarios reply_button: Publicar respuesta reply_link: Responder debates: @@ -188,6 +192,7 @@ es: header: administration: Administrar available_locales: Idiomas disponibles + collaborative_legislation: Procesos legislativos debates: Debates external_link_blog: Blog external_link_opendata: Datos abiertos @@ -217,7 +222,9 @@ es: see_all: Ver propuestas budgets: Presupuestos participativos spending_proposals: "Propuestas de inversión" - legislation: + admin: + watch_form_message: 'Has realizado cambios que no han sido guardados. ¿Seguro que quieres abandonar la página?' + legacy_legislation: help: alt: Selecciona el texto que quieres comentar y pulsa en el botón con el lápiz. text: Para comentar este documento debes %{sign_in} o %{sign_up}. Después selecciona el texto que quieres comentar y pulsa en el botón con el lápiz. @@ -688,13 +695,6 @@ es: user_permission_verify_info: "* Sólo usuarios empadronados." user_permission_verify_my_account: Verificar mi cuenta user_permission_votes: Participar en las votaciones finales* - legislation: - help: - title: "¿Cómo puedo comentar este documento?" - text: "Para comentar este documento debes %{sign_in} o %{sign_up}. Después selecciona el texto que quieres comentar y pulsa en el botón con el lápiz." - text_sign_in: "iniciar sesión" - text_sign_up: "registrarte" - alt: "Selecciona el texto que quieres comentar y pulsa en el botón con el lápiz." invisible_captcha: sentence_for_humans: "Si eres humano, por favor ignora este campo" timestamp_error_message: "Eso ha sido demasiado rápido. Por favor, reenvía el formulario." diff --git a/config/locales/legislation.en.yml b/config/locales/legislation.en.yml new file mode 100644 index 000000000..c60160591 --- /dev/null +++ b/config/locales/legislation.en.yml @@ -0,0 +1,112 @@ +--- +en: + legislation: + annotations: + comments: + see_all: See all + see_complete: See complete + comments_count: + one: "%{count} comment" + other: "%{count} comments" + replies_count: + one: "%{count} reply" + other: "%{count} replies" + cancel: Cancel + publish_comment: Publish Comment + form: + phase_not_open: This phase is not open + login_to_comment: You must %{signin} or %{signup} to leave a comment. + signin: Sign in + signup: Sign up + index: + title: Comments + comments_about: Comments about + see_in_context: See in context + comments_count: + one: "%{count} comment" + other: "%{count} comments" + show: + title: Comment + version_chooser: + seeing_version: Commments for version + see_text: See text draft + draft_versions: + changes: + title: Changes + seeing_changelog_version: Revision changes summary + see_text: See text draft + show: + loading_comments: Loading comments + seeing_version: You're seeing draft version + select_draft_version: Select draft + select_version_submit: see + updated_at: updated at %{date} + see_changes: see changes summary + see_comments: See all comments + text_toc: Table of contents + text_body: Text + text_comments: Comments + processes: + header: + view_process_information: View process information + debate: + empty_questions: There aren't any questions + participate: Participate in the debate + header_full: + title: Participate + description: Description + target: Target + how_to_participate: How to participate + more_info: More information and context + index: + filters: + open: Open processes + next: Next + past: Past + no_open_processes: There aren't open processes + no_next_processes: There aren't planned processes + no_past_processes: There aren't past processes + phase_not_open: + not_open: This phase is not open yet + phase_empty: + empty: Nothing published yet + process: + see_latest_comments: See latest comments + see_latest_comments_title: Comment on this process + shared: + key_dates: "Key dates:" + debate_dates: Debate + draft_publication_date: Draft publication + allegations_dates: Allegations + final_publication_date: Final result publication + questions: + comments: + comment_button: Publish answer + comments_title: Open answers + comments_closed: Closed phase + form: + leave_comment: Leave your answer + question: + comments: + zero: No comments + one: "%{count} comment" + other: "%{count} comments" + debate: Debate + show: + answer_question: Submit answer + next_question: Next question + first_question: First question + share: Share + title: Collaborative legislation process + participation: + phase_not_open: This phase is not open + organizations: Organisations are not permitted to participate in the debate + signin: Sign in + signup: Sign up + unauthenticated: You must %{signin} or %{signup} to participate. + verified_only: Only verified users can participate, %{verify_account}. + verify_account: verify your account + debate_phase_not_open: Debate phase has finished and answers are not accepted anymore + shared: + share: Share + share_comment: Comment on %{version_name} from process draft %{process_name} diff --git a/config/locales/legislation.es.yml b/config/locales/legislation.es.yml new file mode 100644 index 000000000..a81f4a31d --- /dev/null +++ b/config/locales/legislation.es.yml @@ -0,0 +1,112 @@ +--- +es: + legislation: + annotations: + comments: + see_all: Ver todos + see_complete: Ver completo + comments_count: + one: "%{count} comentario" + other: "%{count} comentarios" + replies_count: + one: "%{count} respuesta" + other: "%{count} respuestas" + cancel: Cancelar + publish_comment: Publicar Comentario + form: + phase_not_open: Esta fase del proceso no está abierta + login_to_comment: Necesitas %{signin} o %{signup} para comentar. + signin: iniciar sesión + signup: registrarte + index: + title: Comentarios + see_in_context: Ver en contexto + comments_about: Comentarios sobre + comments_count: + one: "%{count} comentario" + other: "%{count} comentarios" + show: + title: Comentario + version_chooser: + seeing_version: Comentarios para la versión + see_text: Ver borrador del texto + draft_versions: + changes: + title: Cambios + seeing_changelog_version: Resumen de cambios de la revisión + see_text: Ver borrador del texto + show: + loading_comments: Cargando comentarios + seeing_version: Estás viendo la revisión + select_draft_version: Seleccionar borrador + select_version_submit: ver + updated_at: actualizada el %{date} + see_changes: ver resumen de cambios + see_comments: Ver todos los comentarios + text_toc: Índice + text_body: Texto + text_comments: Comentarios + processes: + header: + view_process_information: Ver información del proceso + debate: + empty_questions: No hay preguntas + participate: Realiza tus aportaciones al debate previo participando en los siguientes temas. + header_full: + title: Colabora en la elaboración de la normativa sobre + description: En qué consiste + target: A quién va dirigido + how_to_participate: Cómo puedes participar + more_info: Más información y contexto + index: + filters: + open: Procesos activos + next: Próximamente + past: Terminados + no_open_processes: No hay procesos activos + no_next_processes: No hay procesos planeados + no_past_processes: No hay procesos terminados + phase_not_open: + not_open: Esta fase del proceso todavía no está abierta + phase_empty: + empty: No hay nada publicado todavía + process: + see_latest_comments: Ver últimas aportaciones + see_latest_comments_title: Aportar a este proceso + shared: + key_dates: "Fechas clave:" + debate_dates: Debate previo + draft_publication_date: Publicación borrador + allegations_dates: Alegaciones + final_publication_date: Publicación resultados + questions: + comments: + comment_button: Publicar respuesta + comments_title: Respuestas abiertas + comments_closed: Fase cerrada + form: + leave_comment: Deja tu respuesta + question: + comments: + zero: Sin comentarios + one: "%{count} comentario" + other: "%{count} comentarios" + debate: Debate + show: + answer_question: Enviar respuesta + next_question: Siguiente pregunta + first_question: Primera pregunta + share: Compartir + title: Proceso de legislación colaborativa + participation: + phase_not_open: Esta fase no está abierta + organizations: Las organizaciones no pueden participar en el debate + signin: iniciar sesión + signup: registrarte + unauthenticated: Necesitas %{signin} o %{signup} para participar en el debate. + verified_only: Solo los usuarios verificados pueden participar en el debate, %{verify_account}. + verify_account: verifica tu cuenta + debate_phase_not_open: La fase de debate previo ya ha finalizado y en este momento no se aceptan respuestas + shared: + share: Compartir + share_comment: Comentario sobre la %{version_name} del borrador del proceso %{process_name} diff --git a/config/locales/settings.en.yml b/config/locales/settings.en.yml index d76c64bf3..22f10201b 100755 --- a/config/locales/settings.en.yml +++ b/config/locales/settings.en.yml @@ -38,6 +38,7 @@ en: spending_proposals: Investment projects spending_proposal_features: voting_allowed: Voting on investment projects + legislation: Legislation mailer_from_name: Origin email name mailer_from_address: Origin email address meta_description: "Site description (SEO)" diff --git a/config/locales/settings.es.yml b/config/locales/settings.es.yml index 94493651e..3e78333f6 100644 --- a/config/locales/settings.es.yml +++ b/config/locales/settings.es.yml @@ -38,6 +38,7 @@ es: spending_proposals: Propuestas de inversión spending_proposal_features: voting_allowed: Votaciones sobre propuestas de inversión + legislation: Legislación mailer_from_name: Nombre email remitente mailer_from_address: Dirección email remitente meta_description: "Descripción del sitio (SEO)" diff --git a/config/routes.rb b/config/routes.rb index 4007595db..a7d3affdd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,10 @@ Rails.application.routes.draw do + if Rails.env.development? || Rails.env.staging? + get '/sandbox' => 'sandbox#index' + get '/sandbox/*template' => 'sandbox#show' + end + devise_for :users, controllers: { registrations: 'users/registrations', sessions: 'users/sessions', @@ -88,7 +93,7 @@ Rails.application.routes.draw do resources :stats, only: [:index] - resources :legislations, only: [:show] + resources :legacy_legislations, only: [:show], path: 'legislations' resources :annotations do get :search, on: :collection @@ -100,6 +105,27 @@ Rails.application.routes.draw do end end + namespace :legislation do + resources :processes, only: [:index, :show] do + get :debate + get :draft_publication + get :allegations + get :final_version_publication + resources :questions, only: [:show] do + resources :answers, only: [:create] + end + resources :draft_versions, only: [:show] do + get :go_to_version, on: :collection + get :changes + resources :annotations do + get :search, on: :collection + get :comments + post :new_comment + end + end + end + end + resources :users, only: [:show] do resources :direct_messages, only: [:new, :create, :show] end @@ -244,6 +270,13 @@ Rails.application.routes.draw do get :direct_messages, on: :collection end + namespace :legislation do + resources :processes do + resources :questions + resources :draft_versions + end + end + namespace :api do resource :stats, only: :show end diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 9898ad46f..9bcb936cf 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -35,6 +35,7 @@ Setting.create(key: 'feature.twitter_login', value: "true") Setting.create(key: 'feature.facebook_login', value: "true") Setting.create(key: 'feature.google_login', value: "true") Setting.create(key: 'feature.signature_sheets', value: "true") +Setting.create(key: 'feature.legislation', value: "true") Setting.create(key: 'per_page_code_head', value: "") Setting.create(key: 'per_page_code_body', value: "") Setting.create(key: 'comments_body_max_length', value: '1000') @@ -464,6 +465,7 @@ Legislation.create!(title: 'Participatory Democracy', body: 'In order to achieve puts " ✅" print "Ignoring flags in Debates, comments & proposals" + Debate.flagged.reorder("RANDOM()").limit(10).each(&:ignore_flag) Comment.flagged.reorder("RANDOM()").limit(30).each(&:ignore_flag) Proposal.flagged.reorder("RANDOM()").limit(10).each(&:ignore_flag) @@ -632,4 +634,34 @@ print "Creating Poll Voters" end puts " ✅" -puts "All dev seeds created successfuly 👍" +print "Creating legislation processes" + +(1..5).each do |i| + process = ::Legislation::Process.create!(title: Faker::Lorem.sentence(3).truncate(60), + description: Faker::Lorem.paragraphs.join("\n\n"), + target: Faker::Lorem.paragraphs.join("\n\n"), + how_to_participate: Faker::Lorem.paragraphs.join("\n\n"), + additional_info: Faker::Lorem.paragraphs.join("\n\n"), + start_date: Date.current - 3.days, + end_date: Date.current + 3.days, + debate_start_date: Date.current - 3.days, + debate_end_date: Date.current - 1.day, + draft_publication_date: Date.current + 1.day, + allegations_start_date: Date.current + 2.days, + allegations_end_date: Date.current + 3.days, + final_publication_date: Date.current + 4.days + ) + puts " #{process.title}" +end + +::Legislation::Process.all.each do |process| + (1..3).each do |i| + version = process.draft_versions.create!(title: "Version #{i}", + body: Faker::Lorem.paragraphs.join("\n\n") + ) + puts " #{version.title}" + end +end + +puts " ✅" +puts "All dev seeds created successfuly 👍" \ No newline at end of file diff --git a/db/migrate/20161117115841_rename_legislations_to_legacy_legislations.rb b/db/migrate/20161117115841_rename_legislations_to_legacy_legislations.rb new file mode 100644 index 000000000..d8eaa76c0 --- /dev/null +++ b/db/migrate/20161117115841_rename_legislations_to_legacy_legislations.rb @@ -0,0 +1,6 @@ +class RenameLegislationsToLegacyLegislations < ActiveRecord::Migration + def change + rename_table :legislations, :legacy_legislations + rename_column :annotations, :legislation_id, :legacy_legislation_id + end +end diff --git a/db/migrate/20161117135624_create_legislation_processes.rb b/db/migrate/20161117135624_create_legislation_processes.rb new file mode 100644 index 000000000..099a84e65 --- /dev/null +++ b/db/migrate/20161117135624_create_legislation_processes.rb @@ -0,0 +1,23 @@ +class CreateLegislationProcesses < ActiveRecord::Migration + def change + create_table :legislation_processes do |t| + t.string :title + t.text :description + t.text :target + t.text :how_to_participate + t.text :additional_info + t.date :start_date, index: true + t.date :end_date, index: true + t.date :debate_start_date, index: true + t.date :debate_end_date, index: true + t.date :draft_publication_date, index: true + t.date :allegations_start_date, index: true + t.date :allegations_end_date, index: true + t.date :final_publication_date, index: true + + t.datetime :hidden_at, index: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20161205110441_create_legislation_draft_versions.rb b/db/migrate/20161205110441_create_legislation_draft_versions.rb new file mode 100644 index 000000000..1eb15eebe --- /dev/null +++ b/db/migrate/20161205110441_create_legislation_draft_versions.rb @@ -0,0 +1,16 @@ +class CreateLegislationDraftVersions < ActiveRecord::Migration + def change + create_table :legislation_draft_versions do |t| + t.references :legislation_process, index: true, foreign_key: true + t.string :title + t.text :changelog + t.string :status, index: true, default: :draft + t.boolean :final_version, default: false + t.text :body + + t.datetime :hidden_at, index: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20161213144031_add_body_html_to_draft_versions.rb b/db/migrate/20161213144031_add_body_html_to_draft_versions.rb new file mode 100644 index 000000000..c0a05c809 --- /dev/null +++ b/db/migrate/20161213144031_add_body_html_to_draft_versions.rb @@ -0,0 +1,5 @@ +class AddBodyHtmlToDraftVersions < ActiveRecord::Migration + def change + add_column :legislation_draft_versions, :body_html, :text + end +end diff --git a/db/migrate/20161220120037_create_legislation_questions.rb b/db/migrate/20161220120037_create_legislation_questions.rb new file mode 100644 index 000000000..41bc3497f --- /dev/null +++ b/db/migrate/20161220120037_create_legislation_questions.rb @@ -0,0 +1,23 @@ +class CreateLegislationQuestions < ActiveRecord::Migration + def change + create_table :legislation_questions do |t| + t.references :legislation_process, index: true + t.text :title + t.integer :answers_count, default: 0 + + t.datetime :hidden_at, index: true + + t.timestamps null: false + end + + create_table :legislation_question_options do |t| + t.references :legislation_question, index: true + t.string :value + t.integer :answers_count, default: 0 + + t.datetime :hidden_at, index: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20161222115744_create_legislation_answers.rb b/db/migrate/20161222115744_create_legislation_answers.rb new file mode 100644 index 000000000..0c8170d68 --- /dev/null +++ b/db/migrate/20161222115744_create_legislation_answers.rb @@ -0,0 +1,13 @@ +class CreateLegislationAnswers < ActiveRecord::Migration + def change + create_table :legislation_answers do |t| + t.references :legislation_question, index: true + t.references :legislation_question_option, index: true + t.references :user, index: true + + t.datetime :hidden_at, index: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20161222171716_add_comments_count_to_legislation_questions.rb b/db/migrate/20161222171716_add_comments_count_to_legislation_questions.rb new file mode 100644 index 000000000..e71a8879b --- /dev/null +++ b/db/migrate/20161222171716_add_comments_count_to_legislation_questions.rb @@ -0,0 +1,5 @@ +class AddCommentsCountToLegislationQuestions < ActiveRecord::Migration + def change + add_column :legislation_questions, :comments_count, :integer, default: 0 + end +end diff --git a/db/migrate/20161222180927_add_author_to_legislation_questions.rb b/db/migrate/20161222180927_add_author_to_legislation_questions.rb new file mode 100644 index 000000000..a06190045 --- /dev/null +++ b/db/migrate/20161222180927_add_author_to_legislation_questions.rb @@ -0,0 +1,5 @@ +class AddAuthorToLegislationQuestions < ActiveRecord::Migration + def change + add_column :legislation_questions, :author_id, :integer + end +end diff --git a/db/migrate/20161229213217_add_toc_html_to_draft_versions.rb b/db/migrate/20161229213217_add_toc_html_to_draft_versions.rb new file mode 100644 index 000000000..f71070e52 --- /dev/null +++ b/db/migrate/20161229213217_add_toc_html_to_draft_versions.rb @@ -0,0 +1,5 @@ +class AddTocHtmlToDraftVersions < ActiveRecord::Migration + def change + add_column :legislation_draft_versions, :toc_html, :text + end +end diff --git a/db/migrate/20170103125835_create_legislation_annotations.rb b/db/migrate/20170103125835_create_legislation_annotations.rb new file mode 100644 index 000000000..723bf3813 --- /dev/null +++ b/db/migrate/20170103125835_create_legislation_annotations.rb @@ -0,0 +1,16 @@ +class CreateLegislationAnnotations < ActiveRecord::Migration + def change + create_table :legislation_annotations do |t| + t.string :quote + t.text :ranges + t.text :text + + t.references :legislation_draft_version, index: true + t.references :user, index: true + + t.datetime :hidden_at, index: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170105120836_add_comments_count_to_legislation_annotation.rb b/db/migrate/20170105120836_add_comments_count_to_legislation_annotation.rb new file mode 100644 index 000000000..39b2e6387 --- /dev/null +++ b/db/migrate/20170105120836_add_comments_count_to_legislation_annotation.rb @@ -0,0 +1,5 @@ +class AddCommentsCountToLegislationAnnotation < ActiveRecord::Migration + def change + add_column :legislation_annotations, :comments_count, :integer, default: 0 + end +end diff --git a/db/migrate/20170105212047_rename_user_to_author_in_legislation_annotations.rb b/db/migrate/20170105212047_rename_user_to_author_in_legislation_annotations.rb new file mode 100644 index 000000000..55b70395b --- /dev/null +++ b/db/migrate/20170105212047_rename_user_to_author_in_legislation_annotations.rb @@ -0,0 +1,5 @@ +class RenameUserToAuthorInLegislationAnnotations < ActiveRecord::Migration + def change + rename_column :legislation_annotations, :user_id, :author_id + end +end diff --git a/db/migrate/20170125093101_add_ranges_fields_to_legislation_annotations.rb b/db/migrate/20170125093101_add_ranges_fields_to_legislation_annotations.rb new file mode 100644 index 000000000..69ae9df65 --- /dev/null +++ b/db/migrate/20170125093101_add_ranges_fields_to_legislation_annotations.rb @@ -0,0 +1,10 @@ +class AddRangesFieldsToLegislationAnnotations < ActiveRecord::Migration + def change + add_column :legislation_annotations, :range_start, :string + add_column :legislation_annotations, :range_start_offset, :integer + add_column :legislation_annotations, :range_end, :string + add_column :legislation_annotations, :range_end_offset, :integer + + add_index :legislation_annotations, [:range_start, :range_end] + end +end diff --git a/db/migrate/20170210083026_add_context_for_legislation_annotations.rb b/db/migrate/20170210083026_add_context_for_legislation_annotations.rb new file mode 100644 index 000000000..8bf19a64c --- /dev/null +++ b/db/migrate/20170210083026_add_context_for_legislation_annotations.rb @@ -0,0 +1,5 @@ +class AddContextForLegislationAnnotations < ActiveRecord::Migration + def change + add_column :legislation_annotations, :context, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 8a34df55d..6e2c2214e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -54,13 +54,13 @@ ActiveRecord::Schema.define(version: 20170519084239) do t.string "quote" t.text "ranges" t.text "text" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "user_id" - t.integer "legislation_id" + t.integer "legacy_legislation_id" end - add_index "annotations", ["legislation_id"], name: "index_annotations_on_legislation_id", using: :btree + add_index "annotations", ["legacy_legislation_id"], name: "index_annotations_on_legacy_legislation_id", using: :btree add_index "annotations", ["user_id"], name: "index_annotations_on_user_id", using: :btree create_table "banners", force: :cascade do |t| @@ -340,13 +340,122 @@ ActiveRecord::Schema.define(version: 20170519084239) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree - create_table "legislations", force: :cascade do |t| + create_table "legacy_legislations", force: :cascade do |t| t.string "title" t.text "body" t.datetime "created_at", null: false t.datetime "updated_at", null: false end + create_table "legislation_annotations", force: :cascade do |t| + t.string "quote" + t.text "ranges" + t.text "text" + t.integer "legislation_draft_version_id" + t.integer "author_id" + t.datetime "hidden_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "comments_count", default: 0 + t.string "range_start" + t.integer "range_start_offset" + t.string "range_end" + t.integer "range_end_offset" + t.text "context" + end + + add_index "legislation_annotations", ["author_id"], name: "index_legislation_annotations_on_author_id", using: :btree + add_index "legislation_annotations", ["hidden_at"], name: "index_legislation_annotations_on_hidden_at", using: :btree + add_index "legislation_annotations", ["legislation_draft_version_id"], name: "index_legislation_annotations_on_legislation_draft_version_id", using: :btree + add_index "legislation_annotations", ["range_start", "range_end"], name: "index_legislation_annotations_on_range_start_and_range_end", using: :btree + + create_table "legislation_answers", force: :cascade do |t| + t.integer "legislation_question_id" + t.integer "legislation_question_option_id" + t.integer "user_id" + t.datetime "hidden_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "legislation_answers", ["hidden_at"], name: "index_legislation_answers_on_hidden_at", using: :btree + add_index "legislation_answers", ["legislation_question_id"], name: "index_legislation_answers_on_legislation_question_id", using: :btree + add_index "legislation_answers", ["legislation_question_option_id"], name: "index_legislation_answers_on_legislation_question_option_id", using: :btree + add_index "legislation_answers", ["user_id"], name: "index_legislation_answers_on_user_id", using: :btree + + create_table "legislation_draft_versions", force: :cascade do |t| + t.integer "legislation_process_id" + t.string "title" + t.text "changelog" + t.string "status", default: "draft" + t.boolean "final_version", default: false + t.text "body" + t.datetime "hidden_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "body_html" + t.text "toc_html" + end + + add_index "legislation_draft_versions", ["hidden_at"], name: "index_legislation_draft_versions_on_hidden_at", using: :btree + add_index "legislation_draft_versions", ["legislation_process_id"], name: "index_legislation_draft_versions_on_legislation_process_id", using: :btree + add_index "legislation_draft_versions", ["status"], name: "index_legislation_draft_versions_on_status", using: :btree + + create_table "legislation_processes", force: :cascade do |t| + t.string "title" + t.text "description" + t.text "target" + t.text "how_to_participate" + t.text "additional_info" + t.date "start_date" + t.date "end_date" + t.date "debate_start_date" + t.date "debate_end_date" + t.date "draft_publication_date" + t.date "allegations_start_date" + t.date "allegations_end_date" + t.date "final_publication_date" + t.datetime "hidden_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "legislation_processes", ["allegations_end_date"], name: "index_legislation_processes_on_allegations_end_date", using: :btree + add_index "legislation_processes", ["allegations_start_date"], name: "index_legislation_processes_on_allegations_start_date", using: :btree + add_index "legislation_processes", ["debate_end_date"], name: "index_legislation_processes_on_debate_end_date", using: :btree + add_index "legislation_processes", ["debate_start_date"], name: "index_legislation_processes_on_debate_start_date", using: :btree + add_index "legislation_processes", ["draft_publication_date"], name: "index_legislation_processes_on_draft_publication_date", using: :btree + add_index "legislation_processes", ["end_date"], name: "index_legislation_processes_on_end_date", using: :btree + add_index "legislation_processes", ["final_publication_date"], name: "index_legislation_processes_on_final_publication_date", using: :btree + add_index "legislation_processes", ["hidden_at"], name: "index_legislation_processes_on_hidden_at", using: :btree + add_index "legislation_processes", ["start_date"], name: "index_legislation_processes_on_start_date", using: :btree + + create_table "legislation_question_options", force: :cascade do |t| + t.integer "legislation_question_id" + t.string "value" + t.integer "answers_count", default: 0 + t.datetime "hidden_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "legislation_question_options", ["hidden_at"], name: "index_legislation_question_options_on_hidden_at", using: :btree + add_index "legislation_question_options", ["legislation_question_id"], name: "index_legislation_question_options_on_legislation_question_id", using: :btree + + create_table "legislation_questions", force: :cascade do |t| + t.integer "legislation_process_id" + t.text "title" + t.integer "answers_count", default: 0 + t.datetime "hidden_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "comments_count", default: 0 + t.integer "author_id" + end + + add_index "legislation_questions", ["hidden_at"], name: "index_legislation_questions_on_hidden_at", using: :btree + add_index "legislation_questions", ["legislation_process_id"], name: "index_legislation_questions_on_legislation_process_id", using: :btree + create_table "locks", force: :cascade do |t| t.integer "user_id" t.integer "tries", default: 0 @@ -906,7 +1015,7 @@ ActiveRecord::Schema.define(version: 20170519084239) do add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope", using: :btree add_foreign_key "administrators", "users" - add_foreign_key "annotations", "legislations" + add_foreign_key "annotations", "legacy_legislations" add_foreign_key "annotations", "users" add_foreign_key "failed_census_calls", "poll_officers" add_foreign_key "failed_census_calls", "users" @@ -914,6 +1023,7 @@ ActiveRecord::Schema.define(version: 20170519084239) do add_foreign_key "geozones_polls", "geozones" add_foreign_key "geozones_polls", "polls" add_foreign_key "identities", "users" + add_foreign_key "legislation_draft_versions", "legislation_processes" add_foreign_key "locks", "users" add_foreign_key "managers", "users" add_foreign_key "moderators", "users" diff --git a/db/seeds.rb b/db/seeds.rb index eb6bffcd8..836799a35 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -78,6 +78,7 @@ Setting['feature.google_login'] = true Setting['feature.public_stats'] = true Setting['feature.budgets'] = true Setting['feature.signature_sheets'] = true +Setting['feature.legislation'] = true # Spending proposals feature flags Setting['feature.spending_proposal_features.voting_allowed'] = nil diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb new file mode 100644 index 000000000..abd2577c2 --- /dev/null +++ b/spec/controllers/comments_controller_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +describe CommentsController do + + describe 'POST create' do + before(:each) do + @process = create(:legislation_process, debate_start_date: Date.current - 3.day, debate_end_date: Date.current + 2.days) + @question = create(:legislation_question, process: @process, title: "Question 1") + @user = create(:user, :level_two) + @unverified_user = create(:user) + end + + it 'should create an comment if the comments are open' do + sign_in @user + + expect do + xhr :post, :create, comment: {commentable_id: @question.id, commentable_type: "Legislation::Question", body: "a comment"} + end.to change { @question.reload.comments_count }.by(1) + end + + it 'should not create a comment if the comments are closed' do + sign_in @user + @process.update_attribute(:debate_end_date, Date.current - 1.day) + + expect do + xhr :post, :create, comment: {commentable_id: @question.id, commentable_type: "Legislation::Question", body: "a comment"} + end.to_not change { @question.reload.comments_count } + end + + it 'should not create a comment for unverified users when the commentable requires it' do + sign_in @unverified_user + + expect do + xhr :post, :create, comment: {commentable_id: @question.id, commentable_type: "Legislation::Question", body: "a comment"} + end.to_not change { @question.reload.comments_count } + end + end +end diff --git a/spec/controllers/legislation/annotations_controller_spec.rb b/spec/controllers/legislation/annotations_controller_spec.rb new file mode 100644 index 000000000..5b1fd3d39 --- /dev/null +++ b/spec/controllers/legislation/annotations_controller_spec.rb @@ -0,0 +1,105 @@ +require 'rails_helper' + +describe Legislation::AnnotationsController do + + describe 'POST create' do + before(:each) do + @process = create(:legislation_process, allegations_start_date: Date.current - 3.day, allegations_end_date: Date.current + 2.days) + @draft_version = create(:legislation_draft_version, :published, process: @process, title: "Version 1") + @final_version = create(:legislation_draft_version, :published, :final_version, process: @process, title: "Final version") + @user = create(:user, :level_two) + end + + it 'should create an ahoy event' do + sign_in @user + + post :create, process_id: @process.id, + draft_version_id: @draft_version.id, + legislation_annotation: { + "quote"=>"ipsum", + "ranges"=>[{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}], + "text": "una anotacion" + } + expect(Ahoy::Event.where(name: :legislation_annotation_created).count).to eq 1 + expect(Ahoy::Event.last.properties['legislation_annotation_id']).to eq Legislation::Annotation.last.id + end + + it 'should not create an annotation if the draft version is a final version' do + sign_in @user + + post :create, process_id: @process.id, + draft_version_id: @final_version.id, + legislation_annotation: { + "quote"=>"ipsum", + "ranges"=>[{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}], + "text": "una anotacion" + } + + expect(response).to have_http_status(:not_found) + end + + it 'should create an annotation if the process allegations phase is open' do + sign_in @user + + expect do + xhr :post, :create, process_id: @process.id, + draft_version_id: @draft_version.id, + legislation_annotation: { + "quote"=>"ipsum", + "ranges"=>[{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}], + "text": "una anotacion" + } + end.to change { @draft_version.annotations.count }.by(1) + end + + it 'should not create an annotation if the process allegations phase is not open' do + sign_in @user + @process.update_attribute(:allegations_end_date, Date.current - 1.day) + + expect do + xhr :post, :create, process_id: @process.id, + draft_version_id: @draft_version.id, + legislation_annotation: { + "quote"=>"ipsum", + "ranges"=>[{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}], + "text": "una anotacion" + } + end.to_not change { @draft_version.annotations.count } + end + + it 'should create an annotation by parsing parameters in JSON' do + sign_in @user + + expect do + xhr :post, :create, process_id: @process.id, + draft_version_id: @draft_version.id, + legislation_annotation: { + "quote"=>"ipsum", + "ranges"=>[{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}].to_json, + "text": "una anotacion" + } + end.to change { @draft_version.annotations.count }.by(1) + end + + it 'should create a new comment on an existing annotation when range is the same' do + annotation = create(:legislation_annotation, draft_version: @draft_version, text: "my annotation", + ranges: [{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}], + range_start: "/p[1]", range_start_offset: 6, range_end: "/p[1]", range_end_offset: 11) + sign_in @user + + expect do + xhr :post, :create, process_id: @process.id, + draft_version_id: @draft_version.id, + legislation_annotation: { + "quote"=>"ipsum", + "ranges"=>[{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}], + "text": "una anotacion" + } + end.to_not change { @draft_version.annotations.count } + + expect(annotation.reload.comments_count).to eq(2) + expect(annotation.comments.last.body).to eq("una anotacion") + end + + end +end diff --git a/spec/controllers/legislation/answers_controller_spec.rb b/spec/controllers/legislation/answers_controller_spec.rb new file mode 100644 index 000000000..77d037478 --- /dev/null +++ b/spec/controllers/legislation/answers_controller_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +describe Legislation::AnswersController do + + describe 'POST create' do + before(:each) do + @process = create(:legislation_process, debate_start_date: Date.current - 3.day, debate_end_date: Date.current + 2.days) + @question = create(:legislation_question, process: @process, title: "Question 1") + @question_option = create(:legislation_question_option, question: @question, value: "Yes") + @user = create(:user, :level_two) + end + + it 'should create an ahoy event' do + sign_in @user + + post :create, process_id: @process.id, question_id: @question.id, legislation_answer: { legislation_question_option_id: @question_option.id } + expect(Ahoy::Event.where(name: :legislation_answer_created).count).to eq 1 + expect(Ahoy::Event.last.properties['legislation_answer_id']).to eq Legislation::Answer.last.id + end + + it 'should create an answer if the process debate phase is open' do + sign_in @user + + expect do + xhr :post, :create, process_id: @process.id, question_id: @question.id, legislation_answer: { legislation_question_option_id: @question_option.id } + end.to change { @question.reload.answers_count }.by(1) + end + + it 'should not create an answer if the process debate phase is not open' do + sign_in @user + @process.update_attribute(:debate_end_date, Date.current - 1.day) + + expect do + xhr :post, :create, process_id: @process.id, question_id: @question.id, legislation_answer: { legislation_question_option_id: @question_option.id } + end.to_not change { @question.reload.answers_count } + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 5f3061bf4..8893879dd 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -354,8 +354,8 @@ FactoryGirl.define do end end - factory :legislation do - sequence(:title) { |n| "Legislation #{n}" } + factory :legacy_legislation do + sequence(:title) { |n| "Legacy Legislation #{n}" } body "In order to achieve this..." end @@ -363,7 +363,7 @@ FactoryGirl.define do quote "ipsum" text "Loremp ipsum dolor" ranges [{"start"=>"/div[1]", "startOffset"=>5, "end"=>"/div[1]", "endOffset"=>10}] - legislation + legacy_legislation user end @@ -600,6 +600,112 @@ FactoryGirl.define do sequence(:document_number) { |n| "#{n}A" } end + factory :legislation_process, class: 'Legislation::Process' do + title "A collaborative legislation process" + description "Description of the process" + target "Who will affected by this law?" + how_to_participate "You can participate by answering some questions" + start_date Date.current - 5.days + end_date Date.current + 5.days + debate_start_date Date.current - 5.days + debate_end_date Date.current - 2.days + draft_publication_date Date.current - 1.day + allegations_start_date Date.current + allegations_end_date Date.current + 3.days + final_publication_date Date.current + 5.days + + trait :next do + start_date Date.current + 2.days + end_date Date.current + 8.days + debate_start_date Date.current + 2.days + debate_end_date Date.current + 4.days + draft_publication_date Date.current + 5.day + allegations_start_date Date.current + 5.days + allegations_end_date Date.current + 7.days + final_publication_date Date.current + 8.days + end + + trait :past do + start_date Date.current - 12.days + end_date Date.current - 2.days + debate_start_date Date.current - 12.days + debate_end_date Date.current - 9.days + draft_publication_date Date.current - 8.day + allegations_start_date Date.current - 8.days + allegations_end_date Date.current - 4.days + final_publication_date Date.current - 2.days + end + + trait :in_debate_phase do + start_date Date.current - 5.days + end_date Date.current + 5.days + debate_start_date Date.current - 5.days + debate_end_date Date.current + 1.days + draft_publication_date Date.current + 1.day + allegations_start_date Date.current + 2.days + allegations_end_date Date.current + 3.days + final_publication_date Date.current + 5.days + end + end + + factory :legislation_draft_version, class: 'Legislation::DraftVersion' do + process factory: :legislation_process + title "Version 1" + changelog "What changed in this version" + status "draft" + final_version false + body <<-LOREM_IPSUM +Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Expetenda tincidunt in sed, ex partem placerat sea, porro commodo ex eam. His putant aeterno interesset at. Usu ea mundi tincidunt, omnium virtute aliquando ius ex. Ea aperiri sententiae duo. Usu nullam dolorum quaestio ei, sit vidit facilisis ea. Per ne impedit iracundia neglegentur. Consetetur neglegentur eum ut, vis animal legimus inimicus id. + +His audiam deserunt in, eum ubique voluptatibus te. In reque dicta usu. Ne rebum dissentiet eam, vim omnis deseruisse id. Ullum deleniti vituperata at quo, insolens complectitur te eos, ea pri dico munere propriae. Vel ferri facilis ut, qui paulo ridens praesent ad. Possim alterum qui cu. Accusamus consulatu ius te, cu decore soleat appareat usu. + +Est ei erat mucius quaeque. Ei his quas phaedrum, efficiantur mediocritatem ne sed, hinc oratio blandit ei sed. Blandit gloriatur eam et. Brute noluisse per et, verear disputando neglegentur at quo. Sea quem legere ei, unum soluta ne duo. Ludus complectitur quo te, ut vide autem homero pro. + +Vis id minim dicant sensibus. Pri aliquip conclusionemque ad, ad malis evertitur torquatos his. Has ei solum harum reprimique, id illum saperet tractatos his. Ei omnis soleat antiopam quo. Ad augue inani postulant mel, mel ea qualisque forensibus. + +Lorem salutandi eu mea, eam in soleat iriure assentior. Tamquam lobortis id qui. Ea sanctus democritum mei, per eu alterum electram adversarium. Ea vix probo dicta iuvaret, posse epicurei suavitate eam an, nam et vidit menandri. Ut his accusata petentium. +LOREM_IPSUM + + trait :published do + status "published" + end + + trait :final_version do + final_version true + end + end + + factory :legislation_annotation, class: 'Legislation::Annotation' do + draft_version factory: :legislation_draft_version + author factory: :user + quote "ipsum" + text "a comment" + ranges [{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}] + range_start "/p[1]" + range_start_offset 6 + range_end "/p[1]" + range_end_offset 11 + end + + factory :legislation_question, class: 'Legislation::Question' do + process factory: :legislation_process + title "Question text" + author factory: :user + end + + factory :legislation_question_option, class: 'Legislation::QuestionOption' do + question factory: :legislation_question + sequence(:value) { |n| "Option #{n}" } + end + + factory :legislation_answer, class: 'Legislation::Answer' do + question factory: :legislation_question + question_option factory: :legislation_question_option + user + end + factory :site_customization_page, class: 'SiteCustomization::Page' do slug "example-page" title "Example page" diff --git a/spec/features/admin/legislation/draft_versions_spec.rb b/spec/features/admin/legislation/draft_versions_spec.rb new file mode 100644 index 000000000..439d18d0c --- /dev/null +++ b/spec/features/admin/legislation/draft_versions_spec.rb @@ -0,0 +1,101 @@ +require 'rails_helper' + +feature 'Admin legislation draft versions' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + context "Feature flag" do + + scenario 'Disabled with a feature flag' do + Setting['feature.legislation'] = nil + process = create(:legislation_process) + expect{ visit admin_legislation_process_draft_versions_path(process) }.to raise_exception(FeatureFlags::FeatureDisabled) + end + + end + + context "Index" do + + scenario 'Displaying legislation process draft versions' do + process = create(:legislation_process, title: 'An example legislation process') + draft_version = create(:legislation_draft_version, process: process, title: 'Version 1') + + visit admin_legislation_processes_path(filter: 'all') + + click_link 'An example legislation process' + click_link 'Text' + click_link 'Version 1' + + expect(page).to have_content(draft_version.title) + expect(page).to have_content(draft_version.changelog) + end + end + + context 'Create' do + scenario 'Valid legislation draft version' do + process = create(:legislation_process, title: 'An example legislation process') + + visit admin_root_path + + within('#side_menu') do + click_link "Collaborative Legislation" + end + + click_link "All" + + expect(page).to have_content 'An example legislation process' + + click_link 'An example legislation process' + click_link 'Text' + + click_link 'Create version' + + fill_in 'legislation_draft_version_title', with: 'Version 3' + fill_in 'legislation_draft_version_changelog', with: 'Version 3 changes' + fill_in 'legislation_draft_version_body', with: 'Version 3 body' + + within('.primary-buttons') do + click_button 'Create version' + end + + expect(page).to have_content 'An example legislation process' + expect(page).to have_content 'Version 3' + end + end + + context 'Update' do + scenario 'Valid legislation draft version', :js do + process = create(:legislation_process, title: 'An example legislation process') + draft_version = create(:legislation_draft_version, title: 'Version 1', process: process) + + visit admin_root_path + + within('#side_menu') do + click_link "Collaborative Legislation" + end + + click_link "All" + + expect(page).to have_content 'An example legislation process' + + click_link 'An example legislation process' + click_link 'Text' + + click_link 'Version 1' + + click_link 'Launch text editor' + + fill_in 'legislation_draft_version_title', with: 'Version 1b' + fill_in 'legislation_draft_version_body', with: '# Version 1 body\r\n\r\nParagraph\r\n\r\n>Quote' + + within('.fullscreen') do + click_button 'Save changes' + end + + expect(page).to have_content 'Version 1b' + end + end +end diff --git a/spec/features/admin/legislation/processes_spec.rb b/spec/features/admin/legislation/processes_spec.rb new file mode 100644 index 000000000..2218ae8d5 --- /dev/null +++ b/spec/features/admin/legislation/processes_spec.rb @@ -0,0 +1,85 @@ +require 'rails_helper' + +feature 'Admin legislation processes' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + context "Feature flag" do + + scenario 'Disabled with a feature flag' do + Setting['feature.legislation'] = nil + expect{ visit admin_legislation_processes_path }.to raise_exception(FeatureFlags::FeatureDisabled) + end + + end + + context "Index" do + + scenario 'Displaying legislation processes' do + process = create(:legislation_process) + visit admin_legislation_processes_path(filter: 'all') + + expect(page).to have_content(process.title) + end + end + + context 'Create' do + scenario 'Valid legislation process' do + visit admin_root_path + + within('#side_menu') do + click_link "Collaborative Legislation" + end + + expect(page).to_not have_content 'An example legislation process' + + click_link "New process" + + fill_in 'legislation_process_title', with: 'An example legislation process' + fill_in 'legislation_process_description', with: 'Describing the process' + fill_in 'legislation_process_target', with: 'This thing affects people' + fill_in 'legislation_process_how_to_participate', with: 'You can partipate in this thing by doing...' + + base_date = Date.current + fill_in 'legislation_process[start_date]', with: base_date.strftime("%d/%m/%Y") + fill_in 'legislation_process[end_date]', with: (base_date + 5.days).strftime("%d/%m/%Y") + + fill_in 'legislation_process[debate_start_date]', with: base_date.strftime("%d/%m/%Y") + fill_in 'legislation_process[debate_end_date]', with: (base_date + 2.days).strftime("%d/%m/%Y") + fill_in 'legislation_process[draft_publication_date]', with: (base_date + 3.days).strftime("%d/%m/%Y") + fill_in 'legislation_process[allegations_start_date]', with: (base_date + 3.days).strftime("%d/%m/%Y") + fill_in 'legislation_process[allegations_end_date]', with: (base_date + 5.days).strftime("%d/%m/%Y") + fill_in 'legislation_process[final_publication_date]', with: (base_date + 7.days).strftime("%d/%m/%Y") + + click_button 'Create process' + + expect(page).to have_content 'An example legislation process' + end + end + + context 'Update' do + scenario 'Deactivate debate phase', js: true do + process = create(:legislation_process, title: 'An example legislation process') + visit admin_root_path + + within('#side_menu') do + click_link "Collaborative Legislation" + end + + click_link "An example legislation process" + + expect(page).to have_selector("h1", text: "An example legislation process") + expect(find("#debate_phase_active")).to be_checked + + uncheck "debate_phase_active" + click_button "Save changes" + + expect(page).to have_content "Process updated successfully" + expect(find("#debate_start_date").value).to be_blank + expect(find("#debate_end_date").value).to be_blank + end + end +end diff --git a/spec/features/admin/legislation/questions_spec.rb b/spec/features/admin/legislation/questions_spec.rb new file mode 100644 index 000000000..44698a25c --- /dev/null +++ b/spec/features/admin/legislation/questions_spec.rb @@ -0,0 +1,107 @@ +require 'rails_helper' + +feature 'Admin legislation questions' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + context "Feature flag" do + + scenario 'Disabled with a feature flag' do + Setting['feature.legislation'] = nil + process = create(:legislation_process) + expect{ visit admin_legislation_process_questions_path(process) }.to raise_exception(FeatureFlags::FeatureDisabled) + end + + end + + context "Index" do + + scenario 'Displaying legislation process questions' do + process = create(:legislation_process, title: 'An example legislation process') + question = create(:legislation_question, process: process, title: 'Question 1') + question = create(:legislation_question, process: process, title: 'Question 2') + + visit admin_legislation_processes_path(filter: 'all') + + click_link 'An example legislation process' + click_link 'Debate' + + expect(page).to have_content('Question 1') + expect(page).to have_content('Question 2') + end + end + + context 'Create' do + scenario 'Valid legislation question' do + process = create(:legislation_process, title: 'An example legislation process') + + visit admin_root_path + + within('#side_menu') do + click_link "Collaborative Legislation" + end + + click_link "All" + + expect(page).to have_content 'An example legislation process' + + click_link 'An example legislation process' + click_link 'Debate' + + click_link 'Create question' + + fill_in 'legislation_question_title', with: 'Question 3' + click_button 'Create question' + + expect(page).to have_content 'Question 3' + end + end + + context 'Update' do + scenario 'Valid legislation question', :js do + process = create(:legislation_process, title: 'An example legislation process') + question = create(:legislation_question, title: 'Question 2', process: process) + + visit admin_root_path + + within('#side_menu') do + click_link "Collaborative Legislation" + end + + click_link "All" + + expect(page).to have_content 'An example legislation process' + + click_link 'An example legislation process' + click_link 'Debate' + + click_link 'Question 2' + + fill_in 'legislation_question_title', with: 'Question 2b' + click_button 'Save changes' + + expect(page).to have_content 'Question 2b' + end + end + + context 'Delete' do + scenario 'Legislation question', :js do + process = create(:legislation_process, title: 'An example legislation process') + create(:legislation_question, title: 'Question 1', process: process) + question = create(:legislation_question, title: 'Question 2', process: process) + question_option = create(:legislation_question_option, question: question, value: 'Yes') + create(:legislation_answer, question: question, question_option: question_option) + + visit edit_admin_legislation_process_question_path(process, question) + + click_link 'Delete' + + expect(page).to have_content 'Questions' + expect(page).to have_content 'Question 1' + expect(page).to_not have_content 'Question 2' + end + end +end diff --git a/spec/features/comments/legislation_annotations_spec.rb b/spec/features/comments/legislation_annotations_spec.rb new file mode 100644 index 000000000..eb72036e2 --- /dev/null +++ b/spec/features/comments/legislation_annotations_spec.rb @@ -0,0 +1,511 @@ +require 'rails_helper' +include ActionView::Helpers::DateHelper + +feature 'Commenting legislation questions' do + let(:user) { create :user } + let(:legislation_annotation) { create :legislation_annotation, author: user } + + scenario 'Index' do + 3.times { create(:comment, commentable: legislation_annotation) } + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + expect(page).to have_css('.comment', count: 4) + + comment = Comment.last + within first('.comment') do + expect(page).to have_content comment.user.name + expect(page).to have_content I18n.l(comment.created_at, format: :datetime) + expect(page).to have_content comment.body + end + end + + scenario 'Show' do + parent_comment = create(:comment, commentable: legislation_annotation) + first_child = create(:comment, commentable: legislation_annotation, parent: parent_comment) + second_child = create(:comment, commentable: legislation_annotation, parent: parent_comment) + + visit comment_path(parent_comment) + + expect(page).to have_css(".comment", count: 3) + expect(page).to have_content parent_comment.body + expect(page).to have_content first_child.body + expect(page).to have_content second_child.body + + expect(page).to have_link "Go back to #{legislation_annotation.title}", href: legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + end + + scenario 'Collapsable comments', :js do + parent_comment = legislation_annotation.comments.first + child_comment = create(:comment, body: "First subcomment", commentable: legislation_annotation, parent: parent_comment) + grandchild_comment = create(:comment, body: "Last subcomment", commentable: legislation_annotation, parent: child_comment) + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + expect(page).to have_css('.comment', count: 3) + + find("#comment_#{child_comment.id}_children_arrow").trigger('click') + + expect(page).to have_css('.comment', count: 2) + expect(page).to_not have_content grandchild_comment.body + + find("#comment_#{child_comment.id}_children_arrow").trigger('click') + + expect(page).to have_css('.comment', count: 3) + expect(page).to have_content grandchild_comment.body + + find("#comment_#{parent_comment.id}_children_arrow").trigger('click') + + expect(page).to have_css('.comment', count: 1) + expect(page).to_not have_content child_comment.body + expect(page).to_not have_content grandchild_comment.body + end + + scenario 'Comment order' do + c1 = create(:comment, :with_confidence_score, commentable: legislation_annotation, cached_votes_up: 100, cached_votes_total: 120, created_at: Time.current - 2) + c2 = create(:comment, :with_confidence_score, commentable: legislation_annotation, cached_votes_up: 10, cached_votes_total: 12, created_at: Time.current - 1) + c3 = create(:comment, :with_confidence_score, commentable: legislation_annotation, cached_votes_up: 1, cached_votes_total: 2, created_at: Time.current) + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation, order: :most_voted) + + expect(c1.body).to appear_before(c2.body) + expect(c2.body).to appear_before(c3.body) + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation, order: :newest) + + expect(c3.body).to appear_before(c2.body) + expect(c2.body).to appear_before(c1.body) + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation, order: :oldest) + + expect(c1.body).to appear_before(c2.body) + expect(c2.body).to appear_before(c3.body) + end + + scenario 'Creation date works differently in roots and in child comments, even when sorting by confidence_score' do + old_root = create(:comment, commentable: legislation_annotation, created_at: Time.current - 10) + new_root = create(:comment, commentable: legislation_annotation, created_at: Time.current) + old_child = create(:comment, commentable: legislation_annotation, parent_id: new_root.id, created_at: Time.current - 10) + new_child = create(:comment, commentable: legislation_annotation, parent_id: new_root.id, created_at: Time.current) + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation, order: :most_voted) + + expect(new_root.body).to appear_before(old_root.body) + expect(old_child.body).to appear_before(new_child.body) + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation, order: :newest) + + expect(new_root.body).to appear_before(old_root.body) + expect(new_child.body).to appear_before(old_child.body) + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation, order: :oldest) + + expect(old_root.body).to appear_before(new_root.body) + expect(old_child.body).to appear_before(new_child.body) + end + + scenario 'Turns links into html links' do + create :comment, commentable: legislation_annotation, body: 'Built with http://rubyonrails.org/' + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + within first('.comment') do + expect(page).to have_content 'Built with http://rubyonrails.org/' + expect(page).to have_link('http://rubyonrails.org/', href: 'http://rubyonrails.org/') + expect(find_link('http://rubyonrails.org/')[:rel]).to eq('nofollow') + expect(find_link('http://rubyonrails.org/')[:target]).to eq('_blank') + end + end + + scenario 'Sanitizes comment body for security' do + create :comment, commentable: legislation_annotation, body: " click me http://www.url.com" + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + within first('.comment') do + expect(page).to have_content "click me http://www.url.com" + expect(page).to have_link('http://www.url.com', href: 'http://www.url.com') + expect(page).not_to have_link('click me') + end + end + + scenario 'Paginated comments' do + per_page = 10 + (per_page + 2).times { create(:comment, commentable: legislation_annotation)} + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + expect(page).to have_css('.comment', count: per_page) + within("ul.pagination") do + expect(page).to have_content("1") + expect(page).to have_content("2") + expect(page).to_not have_content("3") + click_link "Next", exact: false + end + + expect(page).to have_css('.comment', count: 3) + end + + feature 'Not logged user' do + scenario 'can not see comments forms' do + create(:comment, commentable: legislation_annotation) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + expect(page).to have_content 'You must Sign in or Sign up to leave a comment' + within('#comments') do + expect(page).to_not have_content 'Write a comment' + expect(page).to_not have_content 'Reply' + end + end + end + + scenario 'Create', :js do + login_as(user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + fill_in "comment-body-legislation_annotation_#{legislation_annotation.id}", with: 'Have you thought about...?' + click_button 'Publish comment' + + within "#comments" do + expect(page).to have_content 'Have you thought about...?' + expect(page).to have_content '(2)' + end + end + + scenario 'Errors on create', :js do + login_as(user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + click_button 'Publish comment' + + expect(page).to have_content "Can't be blank" + end + + scenario 'Reply', :js do + citizen = create(:user, username: 'Ana') + manuela = create(:user, username: 'Manuela') + legislation_annotation = create(:legislation_annotation, author: citizen) + comment = legislation_annotation.comments.first + + login_as(manuela) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment-body-comment_#{comment.id}", with: 'It will be done next week.' + click_button 'Publish reply' + end + + within "#comment_#{comment.id}" do + expect(page).to have_content 'It will be done next week.' + end + + expect(page).to_not have_selector("#js-comment-form-comment_#{comment.id}", visible: true) + end + + scenario 'Errors on reply', :js do + comment = legislation_annotation.comments.first + + login_as(user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + click_button 'Publish reply' + expect(page).to have_content "Can't be blank" + end + + end + + scenario "N replies", :js do + parent = create(:comment, commentable: legislation_annotation) + + 7.times do + create(:comment, commentable: legislation_annotation, parent: parent) + parent = parent.children.first + end + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + expect(page).to have_css(".comment.comment.comment.comment.comment.comment.comment.comment") + end + + scenario "Flagging as inappropriate", :js do + comment = create(:comment, commentable: legislation_annotation) + + login_as(user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + within "#comment_#{comment.id}" do + page.find("#flag-expand-comment-#{comment.id}").click + page.find("#flag-comment-#{comment.id}").click + + expect(page).to have_css("#unflag-expand-comment-#{comment.id}") + end + + expect(Flag.flagged?(user, comment)).to be + end + + scenario "Undoing flagging as inappropriate", :js do + comment = create(:comment, commentable: legislation_annotation) + Flag.flag(user, comment) + + login_as(user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + within "#comment_#{comment.id}" do + page.find("#unflag-expand-comment-#{comment.id}").click + page.find("#unflag-comment-#{comment.id}").click + + expect(page).to have_css("#flag-expand-comment-#{comment.id}") + end + + expect(Flag.flagged?(user, comment)).to_not be + end + + scenario "Flagging turbolinks sanity check", :js do + legislation_annotation = create(:legislation_annotation, text: "Should we change the world?") + comment = create(:comment, commentable: legislation_annotation) + + login_as(user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + within "#comment_#{comment.id}" do + page.find("#flag-expand-comment-#{comment.id}").click + expect(page).to have_selector("#flag-comment-#{comment.id}") + end + end + + scenario "Erasing a comment's author" do + legislation_annotation = create(:legislation_annotation) + comment = create(:comment, commentable: legislation_annotation, body: 'this should be visible') + comment.user.erase + + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + within "#comment_#{comment.id}" do + expect(page).to have_content('User deleted') + expect(page).to have_content('this should be visible') + end + end + + scenario 'Submit button is disabled after clicking', :js do + legislation_annotation = create(:legislation_annotation) + login_as(user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + fill_in "comment-body-legislation_annotation_#{legislation_annotation.id}", with: 'Testing submit button!' + click_button 'Publish comment' + + # The button's text should now be "..." + # This should be checked before the Ajax request is finished + expect(page).to_not have_button 'Publish comment' + + expect(page).to have_content('Testing submit button!') + end + + feature "Moderators" do + scenario "can create comment as a moderator", :js do + moderator = create(:moderator) + + login_as(moderator.user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + fill_in "comment-body-legislation_annotation_#{legislation_annotation.id}", with: "I am moderating!" + check "comment-as-moderator-legislation_annotation_#{legislation_annotation.id}" + click_button "Publish comment" + + within "#comments" do + expect(page).to have_content "I am moderating!" + expect(page).to have_content "Moderator ##{moderator.id}" + expect(page).to have_css "div.is-moderator" + expect(page).to have_css "img.moderator-avatar" + end + end + + scenario "can create reply as a moderator", :js do + citizen = create(:user, username: "Ana") + manuela = create(:user, username: "Manuela") + moderator = create(:moderator, user: manuela) + legislation_annotation = create(:legislation_annotation, author: citizen) + comment = legislation_annotation.comments.first + + login_as(manuela) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment-body-comment_#{comment.id}", with: "I am moderating!" + check "comment-as-moderator-comment_#{comment.id}" + click_button 'Publish reply' + end + + within "#comment_#{comment.id}" do + expect(page).to have_content "I am moderating!" + expect(page).to have_content "Moderator ##{moderator.id}" + expect(page).to have_css "div.is-moderator" + expect(page).to have_css "img.moderator-avatar" + end + + expect(page).to_not have_selector("#js-comment-form-comment_#{comment.id}", visible: true) + end + + scenario "can not comment as an administrator" do + moderator = create(:moderator) + + login_as(moderator.user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + expect(page).to_not have_content "Comment as administrator" + end + end + + feature "Administrators" do + scenario "can create comment as an administrator", :js do + admin = create(:administrator) + + login_as(admin.user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + fill_in "comment-body-legislation_annotation_#{legislation_annotation.id}", with: "I am your Admin!" + check "comment-as-administrator-legislation_annotation_#{legislation_annotation.id}" + click_button "Publish comment" + + within "#comments" do + expect(page).to have_content "I am your Admin!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "div.is-admin" + expect(page).to have_css "img.admin-avatar" + end + end + + scenario "can create reply as an administrator", :js do + citizen = create(:user, username: "Ana") + manuela = create(:user, username: "Manuela") + admin = create(:administrator, user: manuela) + legislation_annotation = create(:legislation_annotation, author: citizen) + comment = legislation_annotation.comments.first + + login_as(manuela) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment-body-comment_#{comment.id}", with: "Top of the world!" + check "comment-as-administrator-comment_#{comment.id}" + click_button 'Publish reply' + end + + within "#comment_#{comment.id}" do + expect(page).to have_content "Top of the world!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "div.is-admin" + expect(page).to have_css "img.admin-avatar" + end + + expect(page).to_not have_selector("#js-comment-form-comment_#{comment.id}", visible: true) + end + + scenario "can not comment as a moderator" do + admin = create(:administrator) + + login_as(admin.user) + visit legislation_process_draft_version_annotation_path(legislation_annotation.draft_version.process, legislation_annotation.draft_version, legislation_annotation) + + expect(page).to_not have_content "Comment as moderator" + end + end + + feature 'Voting comments' do + background do + @manuela = create(:user, verified_at: Time.current) + @pablo = create(:user) + @legislation_annotation = create(:legislation_annotation) + @comment = create(:comment, commentable: @legislation_annotation) + + login_as(@manuela) + end + + scenario 'Show' do + create(:vote, voter: @manuela, votable: @comment, vote_flag: true) + create(:vote, voter: @pablo, votable: @comment, vote_flag: false) + + visit legislation_process_draft_version_annotation_path(@legislation_annotation.draft_version.process, @legislation_annotation.draft_version, @legislation_annotation) + + within("#comment_#{@comment.id}_votes") do + within(".in_favor") do + expect(page).to have_content "1" + end + + within(".against") do + expect(page).to have_content "1" + end + + expect(page).to have_content "2 votes" + end + end + + scenario 'Create', :js do + visit legislation_process_draft_version_annotation_path(@legislation_annotation.draft_version.process, @legislation_annotation.draft_version, @legislation_annotation) + + within("#comment_#{@comment.id}_votes") do + find(".in_favor a").click + + within(".in_favor") do + expect(page).to have_content "1" + end + + within(".against") do + expect(page).to have_content "0" + end + + expect(page).to have_content "1 vote" + end + end + + scenario 'Update', :js do + visit legislation_process_draft_version_annotation_path(@legislation_annotation.draft_version.process, @legislation_annotation.draft_version, @legislation_annotation) + + within("#comment_#{@comment.id}_votes") do + find('.in_favor a').click + find('.against a').click + + within('.in_favor') do + expect(page).to have_content "0" + end + + within('.against') do + expect(page).to have_content "1" + end + + expect(page).to have_content "1 vote" + end + end + + xscenario 'Trying to vote multiple times', :js do + visit legislation_process_draft_version_annotation_path(@legislation_annotation.draft_version.process, @legislation_annotation.draft_version, @legislation_annotation) + + within("#comment_#{@comment.id}_votes") do + find('.in_favor a').click + within('.in_favor') do + expect(page).to have_content "1" + end + + find('.in_favor a').click + within('.in_favor') do + expect(page).to_not have_content "2" + expect(page).to have_content "1" + end + + within('.against') do + expect(page).to have_content "0" + end + + expect(page).to have_content "1 vote" + end + end + end + +end diff --git a/spec/features/comments/legislation_questions_spec.rb b/spec/features/comments/legislation_questions_spec.rb new file mode 100644 index 000000000..35521e89b --- /dev/null +++ b/spec/features/comments/legislation_questions_spec.rb @@ -0,0 +1,526 @@ +require 'rails_helper' +include ActionView::Helpers::DateHelper + +feature 'Commenting legislation questions' do + let(:user) { create :user, :level_two } + let(:process) { create :legislation_process, :in_debate_phase } + let(:legislation_question) { create :legislation_question, process: process } + + scenario 'Index' do + 3.times { create(:comment, commentable: legislation_question) } + + visit legislation_process_question_path(legislation_question.process, legislation_question) + + expect(page).to have_css('.comment', count: 3) + + comment = Comment.last + within first('.comment') do + expect(page).to have_content comment.user.name + expect(page).to have_content I18n.l(comment.created_at, format: :datetime) + expect(page).to have_content comment.body + end + end + + scenario 'Show' do + parent_comment = create(:comment, commentable: legislation_question) + first_child = create(:comment, commentable: legislation_question, parent: parent_comment) + second_child = create(:comment, commentable: legislation_question, parent: parent_comment) + + visit comment_path(parent_comment) + + expect(page).to have_css(".comment", count: 3) + expect(page).to have_content parent_comment.body + expect(page).to have_content first_child.body + expect(page).to have_content second_child.body + + expect(page).to have_link "Go back to #{legislation_question.title}", href: legislation_process_question_path(legislation_question.process, legislation_question) + end + + scenario 'Collapsable comments', :js do + parent_comment = create(:comment, body: "Main comment", commentable: legislation_question) + child_comment = create(:comment, body: "First subcomment", commentable: legislation_question, parent: parent_comment) + grandchild_comment = create(:comment, body: "Last subcomment", commentable: legislation_question, parent: child_comment) + + visit legislation_process_question_path(legislation_question.process, legislation_question) + + expect(page).to have_css('.comment', count: 3) + + find("#comment_#{child_comment.id}_children_arrow").trigger('click') + + expect(page).to have_css('.comment', count: 2) + expect(page).to_not have_content grandchild_comment.body + + find("#comment_#{child_comment.id}_children_arrow").trigger('click') + + expect(page).to have_css('.comment', count: 3) + expect(page).to have_content grandchild_comment.body + + find("#comment_#{parent_comment.id}_children_arrow").trigger('click') + + expect(page).to have_css('.comment', count: 1) + expect(page).to_not have_content child_comment.body + expect(page).to_not have_content grandchild_comment.body + end + + scenario 'Comment order' do + c1 = create(:comment, :with_confidence_score, commentable: legislation_question, cached_votes_up: 100, cached_votes_total: 120, created_at: Time.current - 2) + c2 = create(:comment, :with_confidence_score, commentable: legislation_question, cached_votes_up: 10, cached_votes_total: 12, created_at: Time.current - 1) + c3 = create(:comment, :with_confidence_score, commentable: legislation_question, cached_votes_up: 1, cached_votes_total: 2, created_at: Time.current) + + visit legislation_process_question_path(legislation_question.process, legislation_question, order: :most_voted) + + expect(c1.body).to appear_before(c2.body) + expect(c2.body).to appear_before(c3.body) + + visit legislation_process_question_path(legislation_question.process, legislation_question, order: :newest) + + expect(c3.body).to appear_before(c2.body) + expect(c2.body).to appear_before(c1.body) + + visit legislation_process_question_path(legislation_question.process, legislation_question, order: :oldest) + + expect(c1.body).to appear_before(c2.body) + expect(c2.body).to appear_before(c3.body) + end + + scenario 'Creation date works differently in roots and in child comments, even when sorting by confidence_score' do + old_root = create(:comment, commentable: legislation_question, created_at: Time.current - 10) + new_root = create(:comment, commentable: legislation_question, created_at: Time.current) + old_child = create(:comment, commentable: legislation_question, parent_id: new_root.id, created_at: Time.current - 10) + new_child = create(:comment, commentable: legislation_question, parent_id: new_root.id, created_at: Time.current) + + visit legislation_process_question_path(legislation_question.process, legislation_question, order: :most_voted) + + expect(new_root.body).to appear_before(old_root.body) + expect(old_child.body).to appear_before(new_child.body) + + visit legislation_process_question_path(legislation_question.process, legislation_question, order: :newest) + + expect(new_root.body).to appear_before(old_root.body) + expect(new_child.body).to appear_before(old_child.body) + + visit legislation_process_question_path(legislation_question.process, legislation_question, order: :oldest) + + expect(old_root.body).to appear_before(new_root.body) + expect(old_child.body).to appear_before(new_child.body) + end + + scenario 'Turns links into html links' do + create :comment, commentable: legislation_question, body: 'Built with http://rubyonrails.org/' + + visit legislation_process_question_path(legislation_question.process, legislation_question) + + within first('.comment') do + expect(page).to have_content 'Built with http://rubyonrails.org/' + expect(page).to have_link('http://rubyonrails.org/', href: 'http://rubyonrails.org/') + expect(find_link('http://rubyonrails.org/')[:rel]).to eq('nofollow') + expect(find_link('http://rubyonrails.org/')[:target]).to eq('_blank') + end + end + + scenario 'Sanitizes comment body for security' do + create :comment, commentable: legislation_question, body: " click me http://www.url.com" + + visit legislation_process_question_path(legislation_question.process, legislation_question) + + within first('.comment') do + expect(page).to have_content "click me http://www.url.com" + expect(page).to have_link('http://www.url.com', href: 'http://www.url.com') + expect(page).not_to have_link('click me') + end + end + + scenario 'Paginated comments' do + per_page = 10 + (per_page + 2).times { create(:comment, commentable: legislation_question)} + + visit legislation_process_question_path(legislation_question.process, legislation_question) + + expect(page).to have_css('.comment', count: per_page) + within("ul.pagination") do + expect(page).to have_content("1") + expect(page).to have_content("2") + expect(page).to_not have_content("3") + click_link "Next", exact: false + end + + expect(page).to have_css('.comment', count: 2) + end + + feature 'Not logged user' do + scenario 'can not see comments forms' do + create(:comment, commentable: legislation_question) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + expect(page).to have_content 'You must Sign in or Sign up to leave a comment' + within('#comments') do + expect(page).to_not have_content 'Write a comment' + expect(page).to_not have_content 'Reply' + end + end + end + + scenario 'Create', :js do + login_as(user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + fill_in "comment-body-legislation_question_#{legislation_question.id}", with: 'Have you thought about...?' + click_button 'Publish answer' + + within "#comments" do + expect(page).to have_content 'Have you thought about...?' + expect(page).to have_content '(1)' + end + end + + scenario 'Errors on create', :js do + login_as(user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + click_button 'Publish answer' + + expect(page).to have_content "Can't be blank" + end + + scenario "Unverified user can't create comments", :js do + unverified_user = create :user + login_as(unverified_user) + + visit legislation_process_question_path(legislation_question.process, legislation_question) + + expect(page).to have_content "To participate verify your account" + end + + scenario "Can't create comments if debate phase is not open", :js do + process.update_attributes(debate_start_date: Date.current - 2.days, debate_end_date: Date.current - 1.days) + login_as(user) + + visit legislation_process_question_path(legislation_question.process, legislation_question) + + expect(page).to have_content "Closed phase" + end + + scenario 'Reply', :js do + citizen = create(:user, username: 'Ana') + manuela = create(:user, :level_two, username: 'Manuela') + comment = create(:comment, commentable: legislation_question, user: citizen) + + login_as(manuela) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment-body-comment_#{comment.id}", with: 'It will be done next week.' + click_button 'Publish reply' + end + + within "#comment_#{comment.id}" do + expect(page).to have_content 'It will be done next week.' + end + + expect(page).to_not have_selector("#js-comment-form-comment_#{comment.id}", visible: true) + end + + scenario 'Errors on reply', :js do + comment = create(:comment, commentable: legislation_question, user: user) + + login_as(user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + click_button 'Publish reply' + expect(page).to have_content "Can't be blank" + end + + end + + scenario "N replies", :js do + parent = create(:comment, commentable: legislation_question) + + 7.times do + create(:comment, commentable: legislation_question, parent: parent) + parent = parent.children.first + end + + visit legislation_process_question_path(legislation_question.process, legislation_question) + expect(page).to have_css(".comment.comment.comment.comment.comment.comment.comment.comment") + end + + scenario "Flagging as inappropriate", :js do + comment = create(:comment, commentable: legislation_question) + + login_as(user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + within "#comment_#{comment.id}" do + page.find("#flag-expand-comment-#{comment.id}").click + page.find("#flag-comment-#{comment.id}").click + + expect(page).to have_css("#unflag-expand-comment-#{comment.id}") + end + + expect(Flag.flagged?(user, comment)).to be + end + + scenario "Undoing flagging as inappropriate", :js do + comment = create(:comment, commentable: legislation_question) + Flag.flag(user, comment) + + login_as(user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + within "#comment_#{comment.id}" do + page.find("#unflag-expand-comment-#{comment.id}").click + page.find("#unflag-comment-#{comment.id}").click + + expect(page).to have_css("#flag-expand-comment-#{comment.id}") + end + + expect(Flag.flagged?(user, comment)).to_not be + end + + scenario "Flagging turbolinks sanity check", :js do + legislation_question = create(:legislation_question, process: process, title: "Should we change the world?") + comment = create(:comment, commentable: legislation_question) + + login_as(user) + visit legislation_process_path(legislation_question.process) + click_link "Should we change the world?" + + within "#comment_#{comment.id}" do + page.find("#flag-expand-comment-#{comment.id}").click + expect(page).to have_selector("#flag-comment-#{comment.id}") + end + end + + scenario "Erasing a comment's author" do + comment = create(:comment, commentable: legislation_question, body: 'this should be visible') + comment.user.erase + + visit legislation_process_question_path(legislation_question.process, legislation_question) + within "#comment_#{comment.id}" do + expect(page).to have_content('User deleted') + expect(page).to have_content('this should be visible') + end + end + + scenario 'Submit button is disabled after clicking', :js do + login_as(user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + fill_in "comment-body-legislation_question_#{legislation_question.id}", with: 'Testing submit button!' + click_button 'Publish answer' + + # The button's text should now be "..." + # This should be checked before the Ajax request is finished + expect(page).to_not have_button 'Publish answer' + + expect(page).to have_content('Testing submit button!') + end + + feature "Moderators" do + scenario "can create comment as a moderator", :js do + moderator = create(:moderator) + + login_as(moderator.user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + fill_in "comment-body-legislation_question_#{legislation_question.id}", with: "I am moderating!" + check "comment-as-moderator-legislation_question_#{legislation_question.id}" + click_button "Publish answer" + + within "#comments" do + expect(page).to have_content "I am moderating!" + expect(page).to have_content "Moderator ##{moderator.id}" + expect(page).to have_css "div.is-moderator" + expect(page).to have_css "img.moderator-avatar" + end + end + + scenario "can create reply as a moderator", :js do + citizen = create(:user, username: "Ana") + manuela = create(:user, username: "Manuela") + moderator = create(:moderator, user: manuela) + comment = create(:comment, commentable: legislation_question, user: citizen) + + login_as(manuela) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment-body-comment_#{comment.id}", with: "I am moderating!" + check "comment-as-moderator-comment_#{comment.id}" + click_button 'Publish reply' + end + + within "#comment_#{comment.id}" do + expect(page).to have_content "I am moderating!" + expect(page).to have_content "Moderator ##{moderator.id}" + expect(page).to have_css "div.is-moderator" + expect(page).to have_css "img.moderator-avatar" + end + + expect(page).to_not have_selector("#js-comment-form-comment_#{comment.id}", visible: true) + end + + scenario "can not comment as an administrator" do + moderator = create(:moderator) + + login_as(moderator.user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + expect(page).to_not have_content "Comment as administrator" + end + end + + feature "Administrators" do + scenario "can create comment as an administrator", :js do + admin = create(:administrator) + + login_as(admin.user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + fill_in "comment-body-legislation_question_#{legislation_question.id}", with: "I am your Admin!" + check "comment-as-administrator-legislation_question_#{legislation_question.id}" + click_button "Publish answer" + + within "#comments" do + expect(page).to have_content "I am your Admin!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "div.is-admin" + expect(page).to have_css "img.admin-avatar" + end + end + + scenario "can create reply as an administrator", :js do + citizen = create(:user, username: "Ana") + manuela = create(:user, username: "Manuela") + admin = create(:administrator, user: manuela) + comment = create(:comment, commentable: legislation_question, user: citizen) + + login_as(manuela) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment-body-comment_#{comment.id}", with: "Top of the world!" + check "comment-as-administrator-comment_#{comment.id}" + click_button 'Publish reply' + end + + within "#comment_#{comment.id}" do + expect(page).to have_content "Top of the world!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "div.is-admin" + expect(page).to have_css "img.admin-avatar" + end + + expect(page).to_not have_selector("#js-comment-form-comment_#{comment.id}", visible: true) + end + + scenario "can not comment as a moderator" do + admin = create(:administrator) + + login_as(admin.user) + visit legislation_process_question_path(legislation_question.process, legislation_question) + + expect(page).to_not have_content "Comment as moderator" + end + end + + feature 'Voting comments' do + background do + @manuela = create(:user, verified_at: Time.current) + @pablo = create(:user) + @legislation_question = create(:legislation_question) + @comment = create(:comment, commentable: @legislation_question) + + login_as(@manuela) + end + + scenario 'Show' do + create(:vote, voter: @manuela, votable: @comment, vote_flag: true) + create(:vote, voter: @pablo, votable: @comment, vote_flag: false) + + visit legislation_process_question_path(@legislation_question.process, @legislation_question) + + within("#comment_#{@comment.id}_votes") do + within(".in_favor") do + expect(page).to have_content "1" + end + + within(".against") do + expect(page).to have_content "1" + end + + expect(page).to have_content "2 votes" + end + end + + scenario 'Create', :js do + visit legislation_process_question_path(@legislation_question.process, @legislation_question) + + within("#comment_#{@comment.id}_votes") do + find(".in_favor a").click + + within(".in_favor") do + expect(page).to have_content "1" + end + + within(".against") do + expect(page).to have_content "0" + end + + expect(page).to have_content "1 vote" + end + end + + scenario 'Update', :js do + visit legislation_process_question_path(@legislation_question.process, @legislation_question) + + within("#comment_#{@comment.id}_votes") do + find('.in_favor a').click + find('.against a').click + + within('.in_favor') do + expect(page).to have_content "0" + end + + within('.against') do + expect(page).to have_content "1" + end + + expect(page).to have_content "1 vote" + end + end + + xscenario 'Trying to vote multiple times', :js do + visit legislation_process_question_path(@legislation_question.process, @legislation_question) + + within("#comment_#{@comment.id}_votes") do + find('.in_favor a').click + within('.in_favor') do + expect(page).to have_content "1" + end + + find('.in_favor a').click + within('.in_favor') do + expect(page).to_not have_content "2" + expect(page).to have_content "1" + end + + within('.against') do + expect(page).to have_content "0" + end + + expect(page).to have_content "1 vote" + end + end + end + +end diff --git a/spec/features/legislation_spec.rb b/spec/features/legacy_legislation_spec.rb similarity index 57% rename from spec/features/legislation_spec.rb rename to spec/features/legacy_legislation_spec.rb index 93a82e1e9..e2f1ba915 100644 --- a/spec/features/legislation_spec.rb +++ b/spec/features/legacy_legislation_spec.rb @@ -1,11 +1,11 @@ require 'rails_helper' -feature 'Legislation' do +feature 'Legacy Legislation' do scenario 'Show' do - legislation = create(:legislation, title: 'Change the world', body: 'To achieve this...') + legacy_legislation = create(:legacy_legislation, title: 'Change the world', body: 'To achieve this...') - visit legislation_path(legislation) + visit legacy_legislation_path(legacy_legislation) expect(page).to have_content 'Change the world' expect(page).to have_content 'To achieve this...' @@ -17,11 +17,11 @@ feature 'Legislation' do background { login_as user } scenario 'Create' do - legislation = create(:legislation) + legacy_legislation = create(:legacy_legislation) - visit legislation_path(legislation) + visit legacy_legislation_path(legacy_legislation) - page.find(:css, "#legislation_body").double_click + page.find(:css, "#legacy_legislation_body").double_click page.find(:css, ".annotator-adder button").click fill_in 'annotator-field-0', with: 'this is my annotation' page.find(:css, ".annotator-controls a[href='#save']").click @@ -30,7 +30,7 @@ feature 'Legislation' do first(:css, ".annotator-hl").click expect(page).to have_content "this is my annotation" - visit legislation_path(legislation) + visit legacy_legislation_path(legacy_legislation) expect(page).to have_css ".annotator-hl" first(:css, ".annotator-hl").click @@ -38,10 +38,10 @@ feature 'Legislation' do end scenario 'Update' do - legislation = create(:legislation) - annotation = create(:annotation, legislation: legislation, user: user, text: "my annotation") + legacy_legislation = create(:legacy_legislation) + annotation = create(:annotation, legacy_legislation: legacy_legislation, user: user, text: "my annotation") - visit legislation_path(legislation) + visit legacy_legislation_path(legacy_legislation) expect(page).to have_css ".annotator-hl" page.find(:css, ".annotator-hl").click @@ -55,17 +55,17 @@ feature 'Legislation' do page.find(:css, ".annotator-hl").click expect(page).to have_content "edited annotation" - visit legislation_path(legislation) + visit legacy_legislation_path(legacy_legislation) page.find(:css, ".annotator-hl").click expect(page).to have_content "edited annotation" end scenario 'Destroy' do - legislation = create(:legislation) - annotation = create(:annotation, legislation: legislation, user: user) + legacy_legislation = create(:legacy_legislation) + annotation = create(:annotation, legacy_legislation: legacy_legislation, user: user) - visit legislation_path(legislation) + visit legacy_legislation_path(legacy_legislation) expect(page).to have_css ".annotator-hl" @@ -76,11 +76,11 @@ feature 'Legislation' do end scenario 'Search' do - legislation = create(:legislation) - annotation1 = create(:annotation, legislation: legislation, text: "my annotation", ranges: [{"start"=>"/div[1]", "startOffset"=>5, "end"=>"/div[1]", "endOffset"=>10}]) - annotation2 = create(:annotation, legislation: legislation, text: "my other annotation", ranges: [{"start"=>"/div[1]", "startOffset"=>12, "end"=>"/div[1]", "endOffset"=>19}]) + legacy_legislation = create(:legacy_legislation) + annotation1 = create(:annotation, legacy_legislation: legacy_legislation, text: "my annotation", ranges: [{"start"=>"/div[1]", "startOffset"=>5, "end"=>"/div[1]", "endOffset"=>10}]) + annotation2 = create(:annotation, legacy_legislation: legacy_legislation, text: "my other annotation", ranges: [{"start"=>"/div[1]", "startOffset"=>12, "end"=>"/div[1]", "endOffset"=>19}]) - visit legislation_path(legislation) + visit legacy_legislation_path(legacy_legislation) expect(page).to have_css ".annotator-hl" first(:css, ".annotator-hl").click diff --git a/spec/features/legislation/draft_versions_spec.rb b/spec/features/legislation/draft_versions_spec.rb new file mode 100644 index 000000000..5b2192d46 --- /dev/null +++ b/spec/features/legislation/draft_versions_spec.rb @@ -0,0 +1,270 @@ +require 'rails_helper' + +feature 'Legislation Draft Versions' do + let(:user) { create(:user) } + let(:administrator) do + create(:administrator, user: user) + user + end + + context "See draft text page" do + before(:each) do + @process = create(:legislation_process) + @draft_version_1 = create(:legislation_draft_version, process: @process, title: "Version 1", body: "Body of the first version", status: "published") + @draft_version_2 = create(:legislation_draft_version, process: @process, title: "Version 2", body: "Body of the second version", status: "published") + @draft_version_3 = create(:legislation_draft_version, process: @process, title: "Version 3", body: "Body of the third version", status: "draft") + end + + it "shows the text body for this version" do + visit legislation_process_draft_version_path(@process, @draft_version_1) + + expect(page).to have_content("Body of the first version") + + within('select#draft_version_id') do + expect(page).to have_content("Version 1") + expect(page).to have_content("Version 2") + expect(page).to_not have_content("Version 3") + end + end + + it "shows an unpublished version to admins" do + login_as(administrator) + + visit legislation_process_draft_version_path(@process, @draft_version_3) + + expect(page).to have_content("Body of the third version") + + within('select#draft_version_id') do + expect(page).to have_content("Version 1") + expect(page).to have_content("Version 2") + expect(page).to have_content("Version 3 *") + end + end + + it "switches to another version without js" do + visit legislation_process_draft_version_path(@process, @draft_version_1) + expect(page).to have_content("Body of the first version") + + select("Version 2") + click_button "see" + + expect(page).to_not have_content("Body of the first version") + expect(page).to have_content("Body of the second version") + end + + it "switches to another version with js", :js do + visit legislation_process_draft_version_path(@process, @draft_version_1) + expect(page).to have_content("Body of the first version") + + select("Version 2") + + expect(page).to_not have_content("Body of the first version") + expect(page).to have_content("Body of the second version") + end + + context "for final versions" do + it "does not show the comments panel" do + final_version = create(:legislation_draft_version, process: @process, title: "Final version", body: "Final body", status: "published", final_version: true) + + visit legislation_process_draft_version_path(@process, final_version) + + expect(page).to have_content("Final body") + expect(page).to_not have_content("See all comments") + expect(page).to_not have_content("Comments") + end + end + end + + context "See changes page" do + before(:each) do + @process = create(:legislation_process) + @draft_version_1 = create(:legislation_draft_version, process: @process, title: "Version 1", body: "Body of the first version", changelog: "Changes for first version", status: "published") + @draft_version_2 = create(:legislation_draft_version, process: @process, title: "Version 2", body: "Body of the second version", changelog: "Changes for second version", status: "published") + @draft_version_3 = create(:legislation_draft_version, process: @process, title: "Version 3", body: "Body of the third version", changelog: "Changes for third version", status: "draft") + end + + it "shows the changes for this version" do + visit legislation_process_draft_version_changes_path(@process, @draft_version_1) + + expect(page).to have_content("Changes for first version") + + within('select#draft_version_id') do + expect(page).to have_content("Version 1") + expect(page).to have_content("Version 2") + expect(page).to_not have_content("Version 3") + end + end + + it "shows an unpublished version to admins" do + login_as(administrator) + + visit legislation_process_draft_version_changes_path(@process, @draft_version_3) + + expect(page).to have_content("Changes for third version") + + within('select#draft_version_id') do + expect(page).to have_content("Version 1") + expect(page).to have_content("Version 2") + expect(page).to have_content("Version 3 *") + end + end + + it "switches to another version without js" do + visit legislation_process_draft_version_changes_path(@process, @draft_version_1) + expect(page).to have_content("Changes for first version") + + select("Version 2") + click_button "see" + + expect(page).to_not have_content("Changes for first version") + expect(page).to have_content("Changes for second version") + end + + it "switches to another version with js", :js do + visit legislation_process_draft_version_changes_path(@process, @draft_version_1) + expect(page).to have_content("Changes for first version") + + select("Version 2") + + expect(page).to_not have_content("Changes for first version") + expect(page).to have_content("Changes for second version") + end + end + + context 'Annotations', :js do + let(:user) { create(:user) } + background { login_as user } + + scenario 'Visit as anonymous' do + logout + draft_version = create(:legislation_draft_version, :published) + + visit legislation_process_draft_version_path(draft_version.process, draft_version) + + page.find(:css, ".legislation-annotatable").double_click + page.find(:css, ".annotator-adder button").click + expect(page).to_not have_css('#legislation_annotation_text') + expect(page).to have_content "ou must Sign in or Sign up to leave a comment." + end + + scenario 'Create' do + draft_version = create(:legislation_draft_version, :published) + + visit legislation_process_draft_version_path(draft_version.process, draft_version) + + page.find(:css, ".legislation-annotatable").double_click + page.find(:css, ".annotator-adder button").click + page.click_button "Publish Comment" + expect(page).to have_content "Comment can't be blank" + + fill_in 'legislation_annotation_text', with: 'this is my annotation' + page.click_button "Publish Comment" + + expect(page).to have_css ".annotator-hl" + first(:css, ".annotator-hl").click + expect(page).to have_content "this is my annotation" + + visit legislation_process_draft_version_path(draft_version.process, draft_version) + + expect(page).to have_css ".annotator-hl" + first(:css, ".annotator-hl").click + expect(page).to have_content "this is my annotation" + end + + scenario 'View annotations and comments' do + draft_version = create(:legislation_draft_version, :published) + annotation1 = create(:legislation_annotation, draft_version: draft_version, text: "my annotation", ranges: [{"start"=>"/p[1]", "startOffset"=>5, "end"=>"/p[1]", "endOffset"=>10}]) + annotation2 = create(:legislation_annotation, draft_version: draft_version, text: "my other annotation", ranges: [{"start"=>"/p[1]", "startOffset"=>12, "end"=>"/p[1]", "endOffset"=>19}]) + + visit legislation_process_draft_version_path(draft_version.process, draft_version) + + expect(page).to have_css ".annotator-hl" + first(:css, ".annotator-hl").click + expect(page).to have_content "my annotation" + + all(".annotator-hl")[1].trigger('click') + expect(page).to have_content "my other annotation" + end + + scenario "Publish new comment for an annotation from comments box" do + draft_version = create(:legislation_draft_version, :published) + annotation = create(:legislation_annotation, draft_version: draft_version, text: "my annotation", ranges: [{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}]) + + visit legislation_process_draft_version_path(draft_version.process, draft_version) + + expect(page).to have_css ".annotator-hl" + first(:css, ".annotator-hl").click + expect(page).to have_content "my annotation" + + click_link "Publish Comment" + fill_in "comment[body]", with: "My interesting comment" + click_button "Publish comment" + expect(page).to have_content "My interesting comment" + end + end + + context "Annotations page" do + background do + @draft_version = create(:legislation_draft_version, :published) + @annotation_1 = create(:legislation_annotation, draft_version: @draft_version, text: "my annotation", quote: "ipsum", ranges: [{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}]) + @annotation_2 = create(:legislation_annotation, draft_version: @draft_version, text: "my other annotation", quote: "audiam", ranges: [{"start"=>"/p[3]", "startOffset"=>6, "end"=>"/p[3]", "endOffset"=>11}]) + end + + scenario "See all annotations for a draft version" do + visit legislation_process_draft_version_annotations_path(@draft_version.process, @draft_version) + + expect(page).to have_content "ipsum" + expect(page).to have_content "audiam" + end + + context "switching versions" do + background do + @process = create(:legislation_process) + @draft_version_1 = create(:legislation_draft_version, :published, process: @process, title: "Version 1", body: "Text with quote for version 1") + @annotation_1 = create(:legislation_annotation, draft_version: @draft_version_1, text: "annotation for version 1", quote: "quote for version 1", ranges: [{"start"=>"/p[1]", "startOffset"=>11, "end"=>"/p[1]", "endOffset"=>30}]) + @draft_version_2 = create(:legislation_draft_version, :published, process: @process, title: "Version 2", body: "Text with quote for version 2") + @annotation_1 = create(:legislation_annotation, draft_version: @draft_version_2, text: "annotation for version 2", quote: "quote for version 2", ranges: [{"start"=>"/p[1]", "startOffset"=>11, "end"=>"/p[1]", "endOffset"=>30}]) + end + + scenario "without js" do + visit legislation_process_draft_version_annotations_path(@process, @draft_version_1) + expect(page).to have_content("quote for version 1") + + select("Version 2") + click_button "see" + + expect(page).to_not have_content("quote for version 1") + expect(page).to have_content("quote for version 2") + end + + scenario "with js", :js do + visit legislation_process_draft_version_annotations_path(@process, @draft_version_1) + expect(page).to have_content("quote for version 1") + + select("Version 2") + + expect(page).to_not have_content("quote for version 1") + expect(page).to have_content("quote for version 2") + end + end + end + + context "Annotation comments page" do + background do + @draft_version = create(:legislation_draft_version, :published) + @annotation_1 = create(:legislation_annotation, draft_version: @draft_version, text: "my annotation", quote: "ipsum", ranges: [{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[1]", "endOffset"=>11}]) + @annotation_2 = create(:legislation_annotation, draft_version: @draft_version, text: "my other annotation", quote: "audiam", ranges: [{"start"=>"/p[3]", "startOffset"=>6, "end"=>"/p[3]", "endOffset"=>11}]) + end + + scenario "See one annotation with replies for a draft version" do + visit legislation_process_draft_version_annotation_path(@draft_version.process, @draft_version, @annotation_2) + + expect(page).to_not have_content "ipsum" + expect(page).to_not have_content "my annotation" + + expect(page).to have_content "audiam" + expect(page).to have_content "my other annotation" + end + end + +end diff --git a/spec/features/legislation/processes_spec.rb b/spec/features/legislation/processes_spec.rb new file mode 100644 index 000000000..66356dff8 --- /dev/null +++ b/spec/features/legislation/processes_spec.rb @@ -0,0 +1,123 @@ +require 'rails_helper' + +feature 'Legislation' do + + context 'processes home page' do + + scenario 'Processes can be listed' do + visit legislation_processes_path + expect(page).to have_text "There aren't open processes" + + visit legislation_processes_path(filter: 'next') + expect(page).to have_text "There aren't planned processes" + + visit legislation_processes_path(filter: 'past') + expect(page).to have_text "There aren't past processes" + end + + scenario 'Processes can be listed' do + processes = create_list(:legislation_process, 3) + + visit legislation_processes_path + + processes.each do |process| + expect(page).to have_link(process.title) + end + end + + scenario 'Filtering processes' do + create(:legislation_process, title: "Process open") + create(:legislation_process, :next, title: "Process next") + create(:legislation_process, :past, title: "Process past") + + visit legislation_processes_path + expect(page).to have_content('Process open') + expect(page).to_not have_content('Process next') + expect(page).to_not have_content('Process past') + + visit legislation_processes_path(filter: 'next') + expect(page).to_not have_content('Process open') + expect(page).to have_content('Process next') + expect(page).to_not have_content('Process past') + + visit legislation_processes_path(filter: 'past') + expect(page).to_not have_content('Process open') + expect(page).to_not have_content('Process next') + expect(page).to have_content('Process past') + end + end + + context 'process page' do + context 'debate phase' do + scenario 'not open' do + process = create(:legislation_process, debate_start_date: Date.current + 1.day, debate_end_date: Date.current + 2.days) + + visit legislation_process_path(process) + + expect(page).to have_content("This phase is not open yet") + end + + scenario 'open' do + process = create(:legislation_process, debate_start_date: Date.current - 1.day, debate_end_date: Date.current + 2.days) + + visit legislation_process_path(process) + + expect(page).to have_content("Participate in the debate") + end + end + + context 'draft publication phase' do + scenario 'not open' do + process = create(:legislation_process, draft_publication_date: Date.current + 1.day) + + visit legislation_process_draft_publication_path(process) + + expect(page).to have_content("This phase is not open yet") + end + + scenario 'open' do + process = create(:legislation_process, draft_publication_date: Date.current) + + visit legislation_process_draft_publication_path(process) + + expect(page).to have_content("Nothing published yet") + end + end + + context 'allegations phase' do + scenario 'not open' do + process = create(:legislation_process, allegations_start_date: Date.current + 1.day, allegations_end_date: Date.current + 2.days) + + visit legislation_process_allegations_path(process) + + expect(page).to have_content("This phase is not open yet") + end + + scenario 'open' do + process = create(:legislation_process, allegations_start_date: Date.current - 1.day, allegations_end_date: Date.current + 2.days) + + visit legislation_process_allegations_path(process) + + expect(page).to have_content("Nothing published yet") + end + end + + context 'final version publication phase' do + scenario 'not open' do + process = create(:legislation_process, final_publication_date: Date.current + 1.day) + + visit legislation_process_final_version_publication_path(process) + + expect(page).to have_content("This phase is not open yet") + end + + scenario 'open' do + process = create(:legislation_process, final_publication_date: Date.current) + + visit legislation_process_final_version_publication_path(process) + + expect(page).to have_content("Nothing published yet") + end + end + end +end diff --git a/spec/features/legislation/questions_spec.rb b/spec/features/legislation/questions_spec.rb new file mode 100644 index 000000000..77503b5a9 --- /dev/null +++ b/spec/features/legislation/questions_spec.rb @@ -0,0 +1,110 @@ +require 'rails_helper' + +feature 'Legislation' do + context 'process debate page' do + before(:each) do + @process = create(:legislation_process, debate_start_date: Date.current - 3.day, debate_end_date: Date.current + 2.days) + create(:legislation_question, process: @process, title: "Question 1") + create(:legislation_question, process: @process, title: "Question 2") + create(:legislation_question, process: @process, title: "Question 3") + end + + scenario 'shows question list' do + visit legislation_process_path(@process) + + expect(page).to have_content("Participate in the debate") + + expect(page).to have_content("Question 1") + expect(page).to have_content("Question 2") + expect(page).to have_content("Question 3") + + click_link "Question 1" + + expect(page).to have_content("Question 1") + expect(page).to have_content("Next question") + + click_link "Next question" + + expect(page).to have_content("Question 2") + expect(page).to have_content("Next question") + + click_link "Next question" + + expect(page).to have_content("Question 3") + expect(page).to_not have_content("Next question") + end + + scenario 'shows question page' do + visit legislation_process_question_path(@process, @process.questions.first) + + expect(page).to have_content("Question 1") + expect(page).to have_content("Open answers (0)") + end + + scenario 'shows next question link in question page' do + visit legislation_process_question_path(@process, @process.questions.first) + + expect(page).to have_content("Question 1") + expect(page).to have_content("Next question") + + click_link "Next question" + + expect(page).to have_content("Question 2") + expect(page).to have_content("Next question") + + click_link "Next question" + + expect(page).to have_content("Question 3") + expect(page).to_not have_content("Next question") + end + + scenario 'answer question' do + question = @process.questions.first + create(:legislation_question_option, question: question, value: "Yes") + create(:legislation_question_option, question: question, value: "No") + option = create(:legislation_question_option, question: question, value: "I don't know") + user = create(:user, :level_two) + + login_as(user) + + visit legislation_process_question_path(@process, question) + + expect(page).to have_selector(:radio_button, "Yes") + expect(page).to have_selector(:radio_button, "No") + expect(page).to have_selector(:radio_button, "I don't know") + expect(page).to have_selector(:link_or_button, "Submit answer") + + choose("I don't know") + click_button "Submit answer" + + within(:css, "label.active") do + expect(page).to have_content("I don't know") + expect(page).to_not have_content("Yes") + expect(page).to_not have_content("No") + end + expect(page).to_not have_selector(:link_or_button, "Submit answer") + + expect(question.reload.answers_count).to eq(1) + expect(option.reload.answers_count).to eq(1) + end + + scenario 'cannot answer question when phase not open' do + @process.update_attribute(:debate_end_date, Date.current - 1.day) + question = @process.questions.first + create(:legislation_question_option, question: question, value: "Yes") + create(:legislation_question_option, question: question, value: "No") + create(:legislation_question_option, question: question, value: "I don't know") + user = create(:user, :level_two) + + login_as(user) + + visit legislation_process_question_path(@process, question) + + expect(page).to have_selector(:radio_button, "Yes", disabled: true) + expect(page).to have_selector(:radio_button, "No", disabled: true) + expect(page).to have_selector(:radio_button, "I don't know", disabled: true) + + expect(page).to_not have_selector(:link_or_button, "Submit answer") + end + end +end diff --git a/spec/features/notifications_spec.rb b/spec/features/notifications_spec.rb index 5fbc51736..b5a78c38c 100644 --- a/spec/features/notifications_spec.rb +++ b/spec/features/notifications_spec.rb @@ -1,10 +1,18 @@ require 'rails_helper' feature "Notifications" do + let(:admin_user) { create :user } + let(:administrator) do + create(:administrator, user: admin_user) + admin_user + end let(:author) { create :user } let(:user) { create :user } let(:debate) { create :debate, author: author } let(:proposal) { create :proposal, author: author } + let(:process) { create :legislation_process, :in_debate_phase } + let(:legislation_question) { create(:legislation_question, process: process, author: administrator) } + let(:legislation_annotation) { create(:legislation_annotation, author: author) } scenario "User commented on my debate", :js do login_as user @@ -28,6 +36,29 @@ feature "Notifications" do expect(page).to have_xpath "//a[@href='#{notification_path(Notification.last)}']" end + scenario "User commented on my legislation question", :js do + verified_user = create(:user, :level_two) + login_as verified_user + visit legislation_process_question_path legislation_question.process, legislation_question + + fill_in "comment-body-legislation_question_#{legislation_question.id}", with: "I answered your question" + click_button "Publish answer" + within "#comments" do + expect(page).to have_content "I answered your question" + end + + logout + login_as administrator + visit root_path + + find(".icon-notification").click + + expect(page).to have_css ".notification", count: 1 + + expect(page).to have_content "Someone commented on" + expect(page).to have_xpath "//a[@href='#{notification_path(Notification.last)}']" + end + scenario "Multiple comments on my proposal", :js do login_as user visit proposal_path proposal diff --git a/spec/helpers/text_helper_spec.rb b/spec/helpers/text_helper_spec.rb new file mode 100644 index 000000000..cc9300b7f --- /dev/null +++ b/spec/helpers/text_helper_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +describe TextHelper do + + describe "#first_paragraph" do + it "should return the first paragraph of a text" do + text = "\n\nThis is the first paragraph\n\nThis is the second paragraph\n" + expect(first_paragraph(text)).to eq("This is the first paragraph") + end + + it "should return blank if the text is blank" do + expect(first_paragraph("")).to eq("") + expect(first_paragraph(nil)).to eq("") + end + end + +end diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index 5312221b8..da872e353 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -12,6 +12,7 @@ describe "Abilities::Administrator" do let(:debate) { create(:debate) } let(:comment) { create(:comment) } let(:proposal) { create(:proposal) } + let(:legislation_question) { create(:legislation_question) } let(:hidden_debate) { create(:debate, :hidden) } let(:hidden_comment) { create(:comment, :hidden) } @@ -50,6 +51,9 @@ describe "Abilities::Administrator" do it { should be_able_to(:comment_as_administrator, proposal) } it { should_not be_able_to(:comment_as_moderator, proposal) } + it { should be_able_to(:comment_as_administrator, legislation_question) } + it { should_not be_able_to(:comment_as_moderator, legislation_question) } + it { should be_able_to(:manage, Annotation) } it { should be_able_to(:read, SpendingProposal) } diff --git a/spec/models/abilities/moderator_spec.rb b/spec/models/abilities/moderator_spec.rb index c49fe572a..332f69450 100644 --- a/spec/models/abilities/moderator_spec.rb +++ b/spec/models/abilities/moderator_spec.rb @@ -11,6 +11,7 @@ describe "Abilities::Moderator" do let(:debate) { create(:debate) } let(:comment) { create(:comment) } let(:proposal) { create(:proposal) } + let(:legislation_question) { create(:legislation_question) } let(:own_debate) { create(:debate, author: user) } let(:own_comment) { create(:comment, author: user) } @@ -101,7 +102,9 @@ describe "Abilities::Moderator" do it { should be_able_to(:comment_as_moderator, debate) } it { should be_able_to(:comment_as_moderator, proposal) } + it { should be_able_to(:comment_as_moderator, legislation_question) } it { should_not be_able_to(:comment_as_administrator, debate) } it { should_not be_able_to(:comment_as_administrator, proposal) } + it { should_not be_able_to(:comment_as_administrator, legislation_question) } end end diff --git a/spec/models/legislation/annotation_spec.rb b/spec/models/legislation/annotation_spec.rb new file mode 100644 index 000000000..4cf5c6f59 --- /dev/null +++ b/spec/models/legislation/annotation_spec.rb @@ -0,0 +1,47 @@ +require 'rails_helper' + +RSpec.describe Legislation::Annotation, type: :model do + let(:draft_version) { create(:legislation_draft_version) } + let(:annotation) { create(:legislation_annotation, draft_version: draft_version) } + + it "should be valid" do + expect(draft_version).to be_valid + expect(annotation).to be_valid + end + + it "calculates the context for multinode annotations" do + annotation = create(:legislation_annotation, + draft_version: draft_version, + quote: "ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Expetenda tincidunt in sed, ex partem placerat sea, porro commodo ex eam. His putant aeterno interesset at. Usu ea mundi tincidunt, omnium virtute aliquando ius ex. Ea aperiri sententiae duo. Usu nullam dolorum quaestio ei, sit vidit facilisis ea. Per ne impedit iracundia neglegentur. Consetetur neglegentur eum ut, vis animal legimus inimicus id. + +His audiam", + ranges: [{"start"=>"/p[1]", "startOffset"=>6, "end"=>"/p[3]", "endOffset"=>11}] + ) + + expect(annotation.context).to eq("Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.\n\nExpetenda tincidunt in sed, ex partem placerat sea, porro commodo ex eam. His putant aeterno interesset at. Usu ea mundi tincidunt, omnium virtute aliquando ius ex. Ea aperiri sententiae duo. Usu nullam dolorum quaestio ei, sit vidit facilisis ea. Per ne impedit iracundia neglegentur. Consetetur neglegentur eum ut, vis animal legimus inimicus id.\n\nHis audiamdeserunt in, eum ubique voluptatibus te. In reque dicta usu. Ne rebum dissentiet eam, vim omnis deseruisse id. Ullum deleniti vituperata at quo, insolens complectitur te eos, ea pri dico munere propriae. Vel ferri facilis ut, qui paulo ridens praesent ad. Possim alterum qui cu. Accusamus consulatu ius te, cu decore soleat appareat usu.") + end + + it "calculates the context for multinode annotations 2" do + annotation = create(:legislation_annotation, + draft_version: draft_version, + quote: "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.\r\n\r\nExpetenda tincidunt in sed, ex partem placerat sea, porro commodo ex eam. His putant aeterno interesset at. Usu ea mundi tincidunt, omnium virtute aliquando ius ex. Ea aperiri sententiae duo", + ranges: [{"start"=>"/p[1]", "startOffset"=>273, "end"=>"/p[2]", "endOffset"=>190}] + ) + + expect(annotation.context).to eq("Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.\r\n\r\nExpetenda tincidunt in sed, ex partem placerat sea, porro commodo ex eam. His putant aeterno interesset at. Usu ea mundi tincidunt, omnium virtute aliquando ius ex. Ea aperiri sententiae duo. Usu nullam dolorum quaestio ei, sit vidit facilisis ea. Per ne impedit iracundia neglegentur. Consetetur neglegentur eum ut, vis animal legimus inimicus id.") + end + + it "calculates the context for multinode annotations 3" do + draft_version = create(:legislation_draft_version, body: "The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.\r\n\r\nThe licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.\r\n\r\nWhen we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.\r\n\r\nDevelopers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.\r\n\r\nA secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.\r\n\r\nThe GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.\r\n\r\nAn older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.") + + annotation = create(:legislation_annotation, + draft_version: draft_version, + quote: "By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.\r\n\r\nWhen we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish)", + ranges: [{"start"=>"/p[2]", "startOffset"=>127, "end"=>"/p[3]", "endOffset"=>223}] + ) + + expect(annotation.context).to eq("The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.\r\n\r\nWhen we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.") + end +end diff --git a/spec/models/legislation/answer_spec.rb b/spec/models/legislation/answer_spec.rb new file mode 100644 index 000000000..9f5037081 --- /dev/null +++ b/spec/models/legislation/answer_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +RSpec.describe Legislation::Answer, type: :model do + let(:legislation_answer) { build(:legislation_answer) } + + it "should be valid" do + expect(legislation_answer).to be_valid + end + + it "counts answers" do + question = create(:legislation_question) + option_1 = create(:legislation_question_option, question: question, value: 'Yes') + option_2 = create(:legislation_question_option, question: question, value: 'No') + + answer = create(:legislation_answer, question: question, question_option: option_2) + + expect(answer).to be_valid + expect(question.answers_count).to eq 1 + expect(option_2.answers_count).to eq 1 + expect(option_1.answers_count).to eq 0 + end + + it "can't answer same question more than once" do + question = create(:legislation_question) + option_1 = create(:legislation_question_option, question: question, value: 'Yes') + option_2 = create(:legislation_question_option, question: question, value: 'No') + user = create(:user) + + answer = create(:legislation_answer, question: question, question_option: option_2, user: user) + expect(answer).to be_valid + + second_answer = build(:legislation_answer, question: question, question_option: option_1, user: user) + expect(second_answer).to be_invalid + + expect(question.answers_count).to eq 1 + expect(option_2.answers_count).to eq 1 + expect(option_1.answers_count).to eq 0 + end +end diff --git a/spec/models/legislation/draft_version_spec.rb b/spec/models/legislation/draft_version_spec.rb new file mode 100644 index 000000000..384fc4513 --- /dev/null +++ b/spec/models/legislation/draft_version_spec.rb @@ -0,0 +1,80 @@ +require 'rails_helper' + +RSpec.describe Legislation::DraftVersion, type: :model do + let(:legislation_draft_version) { build(:legislation_draft_version) } + + it "should be valid" do + expect(legislation_draft_version).to be_valid + end + + it "renders and saves the html from the markdown body field" do + legislation_draft_version.body = body_markdown + + legislation_draft_version.save! + + expect(legislation_draft_version.body_html).to eq(body_html) + expect(legislation_draft_version.toc_html).to eq(toc_html) + end + + def body_markdown +<<-BODY_MARKDOWN +# Title 1 + +Some paragraph. + +A list: + +- item 1 +- item 2 + +## Subtitle + +Another paragraph. + +# Title 2 + +Something about this. +BODY_MARKDOWN + end + + def body_html +<<-BODY_HTML +

    Title 1

    + +

    Some paragraph.

    + +

    A list:

    + + + +

    Subtitle

    + +

    Another paragraph.

    + +

    Title 2

    + +

    Something about this.

    +BODY_HTML + end + + def toc_html +<<-TOC_HTML +
    +TOC_HTML + end +end diff --git a/spec/models/legislation/process_spec.rb b/spec/models/legislation/process_spec.rb new file mode 100644 index 000000000..f89a4bfcf --- /dev/null +++ b/spec/models/legislation/process_spec.rb @@ -0,0 +1,251 @@ +require 'rails_helper' + +RSpec.describe Legislation::Process, type: :model do + let(:process) { create(:legislation_process) } + + it "should be valid" do + expect(process).to be_valid + end + + describe "dates validations" do + it "is invalid if debate_start_date is present but debate_end_date is not" do + process = build(:legislation_process, debate_start_date: Date.current, debate_end_date: "") + expect(process).to be_invalid + expect(process.errors.messages[:debate_end_date]).to include("can't be blank") + end + + it "is invalid if debate_end_date is present but debate_start_date is not" do + process = build(:legislation_process, debate_start_date: nil, debate_end_date: Date.current) + expect(process).to be_invalid + expect(process.errors.messages[:debate_start_date]).to include("can't be blank") + end + + it "is invalid if allegations_start_date is present but debate_end_date is not" do + process = build(:legislation_process, allegations_start_date: Date.current, allegations_end_date: "") + expect(process).to be_invalid + expect(process.errors.messages[:allegations_end_date]).to include("can't be blank") + end + + it "is invalid if debate_end_date is present but allegations_start_date is not" do + process = build(:legislation_process, allegations_start_date: nil, allegations_end_date: Date.current) + expect(process).to be_invalid + expect(process.errors.messages[:allegations_start_date]).to include("can't be blank") + end + end + + describe "date ranges validations" do + it "is invalid if end_date is before start_date" do + process = build(:legislation_process, start_date: Date.current, end_date: Date.current - 1.day) + expect(process).to be_invalid + expect(process.errors.messages[:end_date]).to include("must be on or after the start date") + end + + it "is valid if end_date is the same as start_date" do + process = build(:legislation_process, start_date: Date.current - 1.day, end_date: Date.current - 1.day) + expect(process).to be_valid + end + + it "is invalid if debate_end_date is before debate start_date" do + process = build(:legislation_process, debate_start_date: Date.current, debate_end_date: Date.current - 1.day) + expect(process).to be_invalid + expect(process.errors.messages[:debate_end_date]).to include("must be on or after the debate start date") + end + + it "is valid if debate_end_date is the same as debate_start_date" do + process = build(:legislation_process, debate_start_date: Date.current - 1.day, debate_end_date: Date.current - 1.day) + expect(process).to be_valid + end + + it "is invalid if allegations_end_date is before allegations_start_date" do + process = build(:legislation_process, allegations_start_date: Date.current, allegations_end_date: Date.current - 1.day) + expect(process).to be_invalid + expect(process.errors.messages[:allegations_end_date]).to include("must be on or after the allegations start date") + end + + it "is valid if allegations_end_date is the same as allegations_start_date" do + process = build(:legislation_process, allegations_start_date: Date.current - 1.day, allegations_end_date: Date.current - 1.day) + expect(process).to be_valid + end + end + + describe "filter scopes" do + before(:each) do + @process_1 = create(:legislation_process, start_date: Date.current - 2.days, end_date: Date.current + 1.day) + @process_2 = create(:legislation_process, start_date: Date.current + 1.days, end_date: Date.current + 3.days) + @process_3 = create(:legislation_process, start_date: Date.current - 4.days, end_date: Date.current - 3.days) + end + + it "filters open" do + open_processes = ::Legislation::Process.open + + expect(open_processes).to include(@process_1) + expect(open_processes).to_not include(@process_2) + expect(open_processes).to_not include(@process_3) + end + + it "filters next" do + next_processes = ::Legislation::Process.next + + expect(next_processes).to include(@process_2) + expect(next_processes).to_not include(@process_1) + expect(next_processes).to_not include(@process_3) + end + + it "filters past" do + past_processes = ::Legislation::Process.past + + expect(past_processes).to include(@process_3) + expect(past_processes).to_not include(@process_2) + expect(past_processes).to_not include(@process_1) + end + end + + describe "#open_phase?" do + it "checks debate phase" do + # future + process.update_attributes(debate_start_date: Date.current + 2.days, debate_end_date: Date.current + 3.days) + expect(process.open_phase?(:debate)).to be false + + # started + process.update_attributes(debate_start_date: Date.current - 2.days, debate_end_date: Date.current + 1.day) + expect(process.open_phase?(:debate)).to be true + + # starts today + process.update_attributes(debate_start_date: Date.current, debate_end_date: Date.current + 1.day) + expect(process.open_phase?(:debate)).to be true + + # past + process.update_attributes(debate_start_date: Date.current - 2.days, debate_end_date: Date.current - 1.day) + expect(process.open_phase?(:debate)).to be false + end + + it "checks allegations phase" do + + # future + process.update_attributes(allegations_start_date: Date.current + 2.days, allegations_end_date: Date.current + 3.days) + expect(process.open_phase?(:allegations)).to be false + + # started + process.update_attributes(allegations_start_date: Date.current - 2.days, allegations_end_date: Date.current + 1.day) + expect(process.open_phase?(:allegations)).to be true + + # starts today + process.update_attributes(allegations_start_date: Date.current, allegations_end_date: Date.current + 1.day) + expect(process.open_phase?(:allegations)).to be true + + # past + process.update_attributes(allegations_start_date: Date.current - 2.days, allegations_end_date: Date.current - 1.day) + expect(process.open_phase?(:allegations)).to be false + end + + it "checks draft publication phase" do + # future + process.update_attributes(draft_publication_date: Date.current + 2.days) + expect(process.open_phase?(:draft_publication)).to be false + + # past + process.update_attributes(draft_publication_date: Date.current - 2.days) + expect(process.open_phase?(:draft_publication)).to be true + + # starts today + process.update_attributes(draft_publication_date: Date.current) + expect(process.open_phase?(:draft_publication)).to be true + end + + it "checks final version publication phase" do + # future + process.update_attributes(final_publication_date: Date.current + 2.days) + expect(process.open_phase?(:final_version_publication)).to be false + + # past + process.update_attributes(final_publication_date: Date.current - 2.days) + expect(process.open_phase?(:final_version_publication)).to be true + + # starts today + process.update_attributes(final_publication_date: Date.current) + expect(process.open_phase?(:final_version_publication)).to be true + end + end + + describe "#show_phase?" do + it "checks debate phase" do + # future + process.update_attributes(debate_start_date: Date.current + 2.days, debate_end_date: Date.current + 3.days) + expect(process.show_phase?(:debate)).to be false + + # started + process.update_attributes(debate_start_date: Date.current - 2.days, debate_end_date: Date.current + 1.day) + expect(process.show_phase?(:debate)).to be true + + # starts today + process.update_attributes(debate_start_date: Date.current, debate_end_date: Date.current + 1.day) + expect(process.show_phase?(:debate)).to be true + + # past + process.update_attributes(debate_start_date: Date.current - 2.days, debate_end_date: Date.current - 1.day) + expect(process.show_phase?(:debate)).to be true + end + + it "checks allegations phase" do + # future + process.update_attributes(allegations_start_date: Date.current + 2.days, allegations_end_date: Date.current + 3.days) + expect(process.show_phase?(:allegations)).to be false + + # started + process.update_attributes(allegations_start_date: Date.current - 2.days, allegations_end_date: Date.current + 1.day) + expect(process.show_phase?(:allegations)).to be true + + # starts today + process.update_attributes(allegations_start_date: Date.current, allegations_end_date: Date.current + 1.day) + expect(process.show_phase?(:allegations)).to be true + + # past + process.update_attributes(allegations_start_date: Date.current - 2.days, allegations_end_date: Date.current - 1.day) + expect(process.show_phase?(:allegations)).to be true + end + + it "checks draft publication phase" do + # future + process.update_attributes(draft_publication_date: Date.current + 2.days) + expect(process.show_phase?(:draft_publication)).to be false + + # past + process.update_attributes(draft_publication_date: Date.current - 2.days) + expect(process.show_phase?(:draft_publication)).to be true + + # starts today + process.update_attributes(draft_publication_date: Date.current) + expect(process.show_phase?(:draft_publication)).to be true + end + + it "checks final version publication phase" do + # future + process.update_attributes(final_publication_date: Date.current + 2.days) + expect(process.show_phase?(:final_version_publication)).to be false + + # past + process.update_attributes(final_publication_date: Date.current - 2.days) + expect(process.show_phase?(:final_version_publication)).to be true + + # starts today + process.update_attributes(final_publication_date: Date.current) + expect(process.show_phase?(:final_version_publication)).to be true + end + end + + describe "#status" do + it "should detect planned phase" do + process.update_attributes(start_date: Date.current + 2.days) + expect(process.status).to eq(:planned) + end + + it "should detect closed phase" do + process.update_attributes(end_date: Date.current - 2.days) + expect(process.status).to eq(:closed) + end + + it "should detect open phase" do + expect(process.status).to eq(:open) + end + end +end diff --git a/spec/models/legislation/question_option_spec.rb b/spec/models/legislation/question_option_spec.rb new file mode 100644 index 000000000..37b64c07e --- /dev/null +++ b/spec/models/legislation/question_option_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +RSpec.describe Legislation::QuestionOption, type: :model do + let(:legislation_question_option) { build(:legislation_question_option) } + + it "should be valid" do + expect(legislation_question_option).to be_valid + end + + it "should be unique per question" do + question = create(:legislation_question) + valid_question_option = create(:legislation_question_option, question: question, value: "uno") + + invalid_question_option = build(:legislation_question_option, question: question, value: "uno") + + expect(invalid_question_option).to_not be_valid + end +end diff --git a/spec/models/legislation/question_spec.rb b/spec/models/legislation/question_spec.rb new file mode 100644 index 000000000..216b9cbfd --- /dev/null +++ b/spec/models/legislation/question_spec.rb @@ -0,0 +1,62 @@ +require 'rails_helper' + +RSpec.describe Legislation::Question, type: :model do + let(:question) { create(:legislation_question) } + + it "should be valid" do + expect(question).to be_valid + end + + context "can be deleted" do + example "when it has no options or answers" do + question = create(:legislation_question) + + expect do + question.destroy + end.to change { Legislation::Question.count }.by(-1) + end + + example "when it has options but no answers" do + create(:legislation_question_option, question: question, value: "Yes") + create(:legislation_question_option, question: question, value: "No") + + expect do + question.destroy + end.to change { Legislation::Question.count }.by(-1) + end + + example "when it has options and answers" do + option_1 = create(:legislation_question_option, question: question, value: "Yes") + option_2 = create(:legislation_question_option, question: question, value: "No") + create(:legislation_answer, question: question, question_option: option_1) + create(:legislation_answer, question: question, question_option: option_2) + + expect do + question.destroy + end.to change { Legislation::Question.count }.by(-1) + end + end + + describe "#next_question_id" do + let!(:question1) { create(:legislation_question) } + let!(:question2) { create(:legislation_question, legislation_process_id: question1.legislation_process_id) } + + it "should return the next question" do + expect(question1.next_question_id).to eq(question2.id) + end + + it "should return nil" do + expect(question2.next_question_id).to be_nil + end + end + + describe "#first_question_id" do + let!(:question1) { create(:legislation_question) } + let!(:question2) { create(:legislation_question, legislation_process_id: question1.legislation_process_id) } + + it "should return the first question" do + expect(question1.first_question_id).to eq(question1.id) + expect(question2.first_question_id).to eq(question1.id) + end + end +end