diff --git a/CODE_OF_CONDUCT_ES.md b/CODE_OF_CONDUCT_ES.md index dd3835dcd..7e8586adf 100644 --- a/CODE_OF_CONDUCT_ES.md +++ b/CODE_OF_CONDUCT_ES.md @@ -1,22 +1,46 @@ -# Código de conducta +# Código de Conducta convenido para Contribuyentes -Como las personas encargadas de contribuir código y responsables de mantener este proyecto, y con la intención de crear una comunidad abierta en la que sentirse bienvenido acordamos respetar a toda persona que contribuya al proyecto reportando incidencias, creando peticiones de funcionalidades, actualizando la documentación, enviando parches de código o de alguna otra manera. +## Nuestro compromiso -Estamos comprometidos a hacer de la participación en este proyecto una experiencia libre de acoso para todo el mundo, independientemente del nivel de experiencia, género, identidad y expresión de género, orientación sexual, discapacidad, apariencia personal, raza, etnia, edad, religión o nacionalidad. +En el interés de fomentar una comunidad abierta y acogedora, nosotros como contribuyentes y administradores nos comprometemos a hacer de la participación en nuestro proyecto y nuestra comunidad una experiencia libre de acoso para todos, independientemente de la edad, dimensión corporal, discapacidad, etnia, identidad y expresión de género, nivel de experiencia, nacionalidad, apariencia física, raza, religión, identidad u orientación sexual. -Ejemplos de comportamiento inaceptable por parte de los participantes incluyen: +## Nuestros estándares -* El uso de imágenes o lenguaje sexual -* Ataques personales -* Comentarios insultantes/despectivos +Ejemplos de comportamiento que contribuyen a crear un ambiente positivo: + +* Uso de lenguaje amable e inclusivo +* Respeto a diferentes puntos de vista y experiencias +* Aceptación de críticas constructivas +* Enfocarse en lo que es mejor para la comunidad +* Mostrar empatía a otros miembros de la comunidad + +Ejemplos de comportamiento inaceptable por participantes: + +* Uso de lenguaje o imágenes sexuales y atención sexual no deseada +* Comentarios insultantes o despectivos (*trolling*) y ataques personales o políticos * Acoso público o privado -* Publicación de información privada de terceros, como la dirección física o electrónica, sin permiso explícito -* Otras conductas poco éticas o no profesionales +* Publicación de información privada de terceros sin su consentimiento, como direcciones físicas o electrónicas +* Otros tipos de conducta que pudieran considerarse inapropiadas en un entorno profesional. -Los administradores del proyecto tienen el derecho y la responsabilidad de eliminar, editar o rechazar comentarios, commits, código, ediciones de wikis, incidencias y otro tipo de aportaciones que no estén en línea con este Código de Conducta. Por el hecho de adoptar este Código de Conducta, los responsables del proyecto se comprometen a aplicar sus principios de manera justa y consistente en todos los aspectos relacionados con el desarrollo del proyecto. Los responsables del proyecto que no sigan ni obliguen a cumplir este código de conducta pueden ser apartados del equipo del proyecto. +## Nuestras responsabilidades -Este código de conducta tiene validez tanto en el proyecto como en espacios públicos en los que un individuo esté representando al proyecto o a su comunidad. +Los administradores del proyecto son responsables de clarificar los estándares de comportamiento aceptable y se espera que tomen medidas correctivas y apropiadas en respuesta a situaciones de conducta inaceptable. -Las ocurrencias de abuso, acoso u otro comportamiento inaceptable se puede reportar abriendo una incidencia o contactando a uno o más de los miembros del equipo responsable del proyecto. +Los administradores del proyecto tienen el derecho y la responsabilidad de eliminar, editar o rechazar comentarios, *commits*, código, ediciones de documentación, *issues*, y otras contribuciones que no estén alineadas con este Código de Conducta, o de prohibir temporal o permanentemente a cualquier colaborador cuyo comportamiento sea inapropiado, amenazante, ofensivo o perjudicial. -Este Código de Conducta es una adaptación del [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, disponible en [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) +## Alcance + +Este código de conducta aplica tanto a espacios del proyecto como a espacios públicos donde un individuo esté en representación del proyecto o comunidad. Ejemplos de esto incluye el uso de la cuenta oficial de correo electrónico, publicaciones a través de las redes sociales oficiales, o presentaciones con personas designadas en eventos *online* u *offline*. La representación del proyecto puede ser clarificada explicitamente por los administradores del proyecto. + +## Aplicación + +Ejemplos de abuso, acoso u otro tipo de comportamiento inaceptable puede ser reportado al equipo del proyecto en consul@madrid.es. Todas las quejas serán revisadas e investigadas, generando un resultado apropiado a las circunstancias. El equipo del proyecto está obligado a mantener confidencialidad de la persona que reportó el incidente. Detalles específicos acerca de las políticas de aplicación pueden ser publicadas por separado. + +Administradores que no sigan o que no hagan cumplir este Código de Conducta pueden ser eliminados de forma temporal o permanente del equipo administrador. + +## Atribución + +Este Código de Conducta es una adaptación del [Contributor Covenant][homepage], versión 1.4, disponible en [http://contributor-covenant.org/version/1/4/es/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/es/ diff --git a/CUSTOMIZE_EN.md b/CUSTOMIZE_EN.md index 542d11ee7..3a97b96de 100644 --- a/CUSTOMIZE_EN.md +++ b/CUSTOMIZE_EN.md @@ -1,12 +1,12 @@ # Customization -You can modify your own Consul to have your custom visual style, but first you'll have to create a fork from [https://github.com/consul/consul](https://github.com/consul/consul) using Github's "fork" button on top right corner. You can use any other service like Gitlab, but don't forget to put a reference link back to Consul on the footer to comply with project's license (GPL Affero 3). +You can modify your own CONSUL to have your custom visual style, but first you'll have to create a fork from [https://github.com/consul/consul](https://github.com/consul/consul) using Github's "fork" button on top right corner. You can use any other service like Gitlab, but don't forget to put a reference link back to CONSUL on the footer to comply with project's license (GPL Affero 3). -We've created an specific structure where you can overwrite and customize the application in a way that will let you keep updating it from Consul's main repository, without having conflicts on code merging or risking loosing your customization changes. We try to make Consul as vanilla as possible to help other developers onboard the codebase. +We've created an specific structure where you can overwrite and customize the application in a way that will let you keep updating it from CONSUL's main repository, without having conflicts on code merging or risking loosing your customization changes. We try to make CONSUL as vanilla as possible to help other developers onboard the codebase. ## Special Folders and Files -In order to customize your Consul fork, you'll make use of some `custom` folders on the following paths: +In order to customize your CONSUL fork, you'll make use of some `custom` folders on the following paths: * `config/locales/custom/` * `app/assets/images/custom/` @@ -207,7 +207,7 @@ TODO ## Updating -We recommend you to add consul as remote: +We recommend you to add CONSUL as remote: ``` git remote add consul https://github.com/consul/consul diff --git a/CUSTOMIZE_ES.md b/CUSTOMIZE_ES.md index e5bea19ad..26204c4e4 100644 --- a/CUSTOMIZE_ES.md +++ b/CUSTOMIZE_ES.md @@ -1,12 +1,12 @@ # Personalización -Puedes modificar consul y ponerle tu propia imagen, para esto debes primero hacer un fork de [https://github.com/consul/consul](https://github.com/consul/consul) creando un repositorio nuevo en Github. Puedes usar otro servicio como Gitlab, pero no te olvides de poner el enlace en el footer a tu repositorio en cumplimiento con la licencia de este proyecto (GPL Affero 3). +Puedes modificar CONSUL y ponerle tu propia imagen, para esto debes primero hacer un fork de [https://github.com/consul/consul](https://github.com/consul/consul) creando un repositorio nuevo en Github. Puedes usar otro servicio como Gitlab, pero no te olvides de poner el enlace en el footer a tu repositorio en cumplimiento con la licencia de este proyecto (GPL Affero 3). -Hemos creado una estructura específica donde puedes sobreescribir y personalizar la aplicación para que puedas actualizar sin que tengas problemas al hacer merge y se sobreescriban por error tus cambios. Intentamos que Consul sea una aplicación Ruby on Rails lo más plain vanilla posible para facilitar el acceso de nuevas desarrolladoras. +Hemos creado una estructura específica donde puedes sobreescribir y personalizar la aplicación para que puedas actualizar sin que tengas problemas al hacer merge y se sobreescriban por error tus cambios. Intentamos que CONSUL sea una aplicación Ruby on Rails lo más plain vanilla posible para facilitar el acceso de nuevas desarrolladoras. ## Ficheros y directorios especiales -Para adaptarlo puedes hacerlo a través de los directorios que están en custom dentro de: +Para adaptar tu fork de CONSUL puedes utilizar alguno de los directorios `custom` que están en las rutas: * `config/locales/custom/` * `app/assets/images/custom/` @@ -207,7 +207,7 @@ TODO ## Actualizar -Te recomendamos que agregues el remote de consul para facilitar este proceso de merge: +Te recomendamos que agregues el remote de CONSUL para facilitar este proceso de merge: ``` git remote add consul https://github.com/consul/consul diff --git a/Gemfile b/Gemfile index 678845e36..a1a0ecf7a 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,7 @@ gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-google-oauth2', '~> 0.4.0' gem 'omniauth-twitter', '~> 1.4.0' gem 'paperclip', '~> 5.1.0' +gem 'jquery-fileupload-rails' gem 'paranoia', '~> 2.3.1' gem 'pg', '~> 0.20.0' gem 'pg_search', '~> 2.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index f2a314780..c33459f75 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,7 +52,7 @@ GEM safely_block (>= 0.1.1) user_agent_parser uuidtools - airbrussh (1.2.0) + airbrussh (1.3.0) sshkit (>= 1.6.1, != 1.7.0) akami (1.3.1) gyoku (>= 0.4.0) @@ -73,7 +73,7 @@ GEM uniform_notifier (~> 1.10.0) byebug (9.0.6) cancancan (1.16.0) - capistrano (3.8.1) + capistrano (3.8.2) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -87,7 +87,7 @@ GEM capistrano3-delayed-job (1.7.3) capistrano (~> 3.0, >= 3.0.0) daemons (~> 1.2.4) - capybara (2.14.0) + capybara (2.14.4) addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -95,17 +95,17 @@ GEM rack-test (>= 0.5.4) xpath (~> 2.0) chronic (0.10.2) - ckeditor (4.2.3) + ckeditor (4.2.4) cocaine orm_adapter (~> 0.5.0) - climate_control (0.1.0) + climate_control (0.2.0) cliver (0.3.2) cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) - cocoon (1.2.9) - coffee-rails (4.2.1) + cocoon (1.2.10) + coffee-rails (4.2.2) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.2.x) + railties (>= 4.0.0) coffee-script (2.4.1) coffee-script-source execjs @@ -120,11 +120,11 @@ GEM daemons (1.2.4) dalli (2.7.6) database_cleaner (1.5.3) - debug_inspector (0.0.2) - delayed_job (4.1.2) - activesupport (>= 3.0, < 5.1) - delayed_job_active_record (4.1.1) - activerecord (>= 3.0, < 5.1) + debug_inspector (0.0.3) + delayed_job (4.1.3) + activesupport (>= 3.0, < 5.2) + delayed_job_active_record (4.1.2) + activerecord (>= 3.0, < 5.2) delayed_job (>= 3.0, < 5) devise (3.5.10) bcrypt (~> 3.0) @@ -144,10 +144,10 @@ GEM json thread thread_safe - email_spec (2.1.0) + email_spec (2.1.1) htmlentities (~> 4.3.3) launchy (~> 2.1) - mail (~> 2.6.3) + mail (~> 2.6) errbase (0.0.3) erubis (2.7.0) execjs (2.7.0) @@ -158,7 +158,7 @@ GEM railties (>= 3.0.0) faker (1.7.3) i18n (~> 0.5) - faraday (0.11.0) + faraday (0.12.1) multipart-post (>= 1.2, < 3) foundation-rails (6.2.4.0) railties (>= 3.1.0) @@ -170,12 +170,12 @@ GEM activesupport (>= 4.1) railties (>= 4.1) tzinfo (~> 1.2, >= 1.2.2) - geocoder (1.4.3) + geocoder (1.4.4) globalid (0.4.0) activesupport (>= 4.2.0) - graphiql-rails (1.4.1) + graphiql-rails (1.4.2) rails - graphql (1.6.3) + graphql (1.6.4) groupdate (3.2.0) activesupport (>= 3) gyoku (1.3.1) @@ -183,9 +183,10 @@ GEM hashie (3.5.5) highline (1.7.8) htmlentities (4.3.4) - httpi (2.4.1) + httpi (2.4.2) rack - i18n (0.8.4) + socksify + i18n (0.8.6) i18n-tasks (0.9.15) activesupport (>= 4.0.2) ast (>= 2.1.0) @@ -198,8 +199,12 @@ GEM terminal-table (>= 1.5.1) initialjs-rails (0.2.0.5) railties (>= 3.1, < 6.0) - invisible_captcha (0.9.2) + invisible_captcha (0.9.3) rails (>= 3.2.0) + jquery-fileupload-rails (0.4.7) + actionpack (>= 3.1) + railties (>= 3.1) + sass (>= 3.2) jquery-rails (4.3.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -224,7 +229,7 @@ GEM knapsack (1.13.3) rake timecop (>= 0.1.0) - kramdown (1.13.2) + kramdown (1.14.0) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.4.1) @@ -246,7 +251,7 @@ GEM mime-types-data (3.2016.0521) mimemagic (0.3.2) mini_portile2 (2.2.0) - minitest (5.10.2) + minitest (5.10.3) mixlib-cli (1.7.0) mixlib-config (2.2.4) multi_json (1.12.1) @@ -259,9 +264,9 @@ GEM nokogiri (1.8.0) mini_portile2 (~> 2.2.0) nori (2.6.0) - oauth (0.5.1) - oauth2 (1.3.1) - faraday (>= 0.8, < 0.12) + oauth (0.5.3) + oauth2 (1.4.0) + faraday (>= 0.8, < 0.13) jwt (~> 1.0) multi_json (~> 1.3) multi_xml (~> 0.5) @@ -328,7 +333,7 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.2.9) sprockets-rails - rails-assets-markdown-it (8.2.1) + rails-assets-markdown-it (8.2.2) rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) rails-dom-testing (1.0.8) @@ -363,7 +368,7 @@ GEM rspec-mocks (3.6.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.6.0) - rspec-rails (3.6.0) + rspec-rails (3.6.1) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) @@ -386,14 +391,14 @@ GEM sshkit (>= 1.2) safely_block (0.2.0) errbase - sass (3.4.23) + sass (3.4.25) sass-rails (5.0.6) railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - savon (2.11.1) + savon (2.11.2) akami (~> 1.2) builder (>= 2.1.2) gyoku (~> 1.2) @@ -408,12 +413,13 @@ GEM docile (~> 1.1.0) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) - simplecov-html (0.10.0) + simplecov-html (0.10.1) sitemap_generator (5.3.1) builder (~> 3.0) social-share-button (0.10.0) coffee-rails - spring (2.0.1) + socksify (1.7.1) + spring (2.0.2) activesupport (>= 4.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) @@ -424,23 +430,23 @@ GEM babel-source (>= 5.8.11) babel-transpiler sprockets (>= 3.0.0) - sprockets-rails (3.2.0) + sprockets-rails (3.2.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.13.1) + sshkit (1.14.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) term-ansicolor (1.6.0) tins (~> 1.0) - terminal-table (1.7.3) - unicode-display_width (~> 1.1.1) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) thor (0.19.4) thread (0.2.2) thread_safe (0.3.6) tilt (2.0.7) - timecop (0.8.1) - tins (1.13.2) + timecop (0.9.1) + tins (1.15.0) turbolinks (2.5.3) coffee-rails turnout (2.4.0) @@ -452,14 +458,14 @@ GEM thread_safe (~> 0.1) uglifier (3.2.0) execjs (>= 0.3.0, < 3) - unicode-display_width (1.1.3) + unicode-display_width (1.3.0) unicorn (5.3.0) kgio (~> 2.6) raindrops (~> 0.7) uniform_notifier (1.10.0) - user_agent_parser (2.3.0) + user_agent_parser (2.3.1) uuidtools (2.1.5) - warden (1.2.6) + warden (1.2.7) rack (>= 1.0) wasabi (3.5.0) httpi (~> 2.0) @@ -473,7 +479,7 @@ GEM websocket-extensions (0.1.2) whenever (0.9.7) chronic (>= 0.6.3) - xpath (2.0.0) + xpath (2.1.0) nokogiri (~> 1.3) PLATFORMS @@ -515,6 +521,7 @@ DEPENDENCIES i18n-tasks (~> 0.9.15) initialjs-rails (~> 0.2.0.5) invisible_captcha (~> 0.9.2) + jquery-fileupload-rails jquery-rails (~> 4.3.1) jquery-ui-rails (~> 6.0.1) kaminari (~> 1.0.1) @@ -559,4 +566,4 @@ DEPENDENCIES whenever (~> 0.9.7) BUNDLED WITH - 1.15.1 + 1.15.3 diff --git a/README.md b/README.md index 8dbfcd281..6b8eed086 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -![Logo of Consul](https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) +![Logo of CONSUL](https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) -# Consul +# CONSUL Citizen Participation and Open Government Application @@ -17,7 +17,7 @@ Citizen Participation and Open Government Application [![Join the chat at https://gitter.im/consul/consul](https://badges.gitter.im/consul/consul.svg)](https://gitter.im/consul/consul?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/consul/consul/issues?q=is%3Aissue+is%3Aopen+label%3APRs-welcome) -This is the opensource code repository of the eParticipation website originally developed for the Madrid City government eParticipation website +This is the opensource code repository of the eParticipation website CONSUL, originally developed for the Madrid City government eParticipation website ## Current state @@ -33,7 +33,7 @@ Frontend tools used include [SCSS](http://sass-lang.com/) over [Foundation](http **NOTE**: For more detailed instructions check the [docs](https://github.com/consul/consul/tree/master/doc/en/dev_test_setup.md) -Prerequisites: install git, Ruby 2.3.2, bundler gem, ghostscript and PostgreSQL (>=9.4). +Prerequisites: install git, Ruby 2.3.2, bundler gem, and PostgreSQL (>=9.4). ``` git clone https://github.com/consul/consul.git @@ -104,3 +104,7 @@ Code published under AFFERO GPL v3 (see [LICENSE-AGPLv3.txt](LICENSE-AGPLv3.txt) ## Contributions See [CONTRIBUTING.md](CONTRIBUTING.md) + +## Brand guidelines + +If you want to use CONSUL logo you can [download the guidelines](https://raw.githubusercontent.com/consul/consul/master/public/consul_brand.zip) which contains a use guide and different versions and sizes of the logo. \ No newline at end of file diff --git a/README_ES.md b/README_ES.md index 7b9267351..155ab957f 100644 --- a/README_ES.md +++ b/README_ES.md @@ -1,6 +1,6 @@ -![Logotipo de Consul](https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) +![Logotipo de CONSUL](https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) -# Consul +# CONSUL Aplicación de Participación Ciudadana y Gobierno Abierto @@ -17,7 +17,7 @@ Aplicación de Participación Ciudadana y Gobierno Abierto [![Join the chat at https://gitter.im/consul/consul](https://badges.gitter.im/consul/consul.svg)](https://gitter.im/consul/consul?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/consul/consul/issues?q=is%3Aissue+is%3Aopen+label%3APRs-welcome) -Este es el repositorio de código abierto de la Aplicación de Participación Ciudadana Consul, creada originariamente por el Ayuntamiento de Madrid. +Este es el repositorio de código abierto de la Aplicación de Participación Ciudadana CONSUL, creada originariamente por el Ayuntamiento de Madrid. ## Estado del proyecto @@ -32,7 +32,7 @@ Las herramientas utilizadas para el frontend no están cerradas aún. Los estilo **NOTA**: para unas instrucciones más detalladas consulta la [documentación](https://github.com/consul/consul/tree/master/doc/es/dev_test_setup.md) -Prerequisitos: tener instalado git, Ruby 2.3.2, la gema `bundler`, ghostscript y PostgreSQL (9.4 o superior). +Prerequisitos: tener instalado git, Ruby 2.3.2, la gema `bundler` y PostgreSQL (9.4 o superior). ``` @@ -101,3 +101,7 @@ El código de este proyecto está publicado bajo la licencia AFFERO GPL v3 (ver ## Contribuciones Ver fichero [CONTRIBUTING_ES.md](CONTRIBUTING_ES.md) + +## Guía de estilo + +Si quieres usar el logo de CONSUL puedes [descargar la guía de estilo](https://raw.githubusercontent.com/consul/consul/master/public/consul_brand.zip) que contiene una guía de uso y diferentes versiones y tamaños del logo. \ No newline at end of file diff --git a/app/assets/images/apple-touch-icon-200.png b/app/assets/images/apple-touch-icon-200.png index 512569d48..61216ad39 100644 Binary files a/app/assets/images/apple-touch-icon-200.png and b/app/assets/images/apple-touch-icon-200.png differ diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico index a68af1ef8..187043296 100644 Binary files a/app/assets/images/favicon.ico and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/help/help_icon_budgets.png b/app/assets/images/help/help_icon_budgets.png new file mode 100644 index 000000000..f8a909d7e Binary files /dev/null and b/app/assets/images/help/help_icon_budgets.png differ diff --git a/app/assets/images/help/help_icon_debates.png b/app/assets/images/help/help_icon_debates.png new file mode 100644 index 000000000..c8d59e4c1 Binary files /dev/null and b/app/assets/images/help/help_icon_debates.png differ diff --git a/app/assets/images/help/help_icon_legislation_processes.png b/app/assets/images/help/help_icon_legislation_processes.png new file mode 100644 index 000000000..9dd93ad8c Binary files /dev/null and b/app/assets/images/help/help_icon_legislation_processes.png differ diff --git a/app/assets/images/help/help_icon_polls.png b/app/assets/images/help/help_icon_polls.png new file mode 100644 index 000000000..503f8642d Binary files /dev/null and b/app/assets/images/help/help_icon_polls.png differ diff --git a/app/assets/images/help/help_icon_proposals.png b/app/assets/images/help/help_icon_proposals.png new file mode 100644 index 000000000..05861d042 Binary files /dev/null and b/app/assets/images/help/help_icon_proposals.png differ diff --git a/app/assets/images/icon_home.png b/app/assets/images/icon_home.png deleted file mode 100644 index 2284856eb..000000000 Binary files a/app/assets/images/icon_home.png and /dev/null differ diff --git a/app/assets/images/logo_email.png b/app/assets/images/logo_email.png index ca2d6d54d..09bc18d63 100644 Binary files a/app/assets/images/logo_email.png and b/app/assets/images/logo_email.png differ diff --git a/app/assets/images/logo_header.png b/app/assets/images/logo_header.png index ef5c4f6ff..9bce3cef8 100644 Binary files a/app/assets/images/logo_header.png and b/app/assets/images/logo_header.png differ diff --git a/app/assets/images/social-media-icon.png b/app/assets/images/social-media-icon.png index 512569d48..61216ad39 100644 Binary files a/app/assets/images/social-media-icon.png and b/app/assets/images/social-media-icon.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index cf2758b78..2ff87100a 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -14,6 +14,7 @@ //= require jquery_ujs //= require jquery-ui/widgets/datepicker //= require jquery-ui/i18n/datepicker-es +//= require jquery-fileupload/basic //= require foundation //= require turbolinks //= require ckeditor/loader @@ -59,6 +60,7 @@ //= require legislation_annotatable //= require watch_form_changes //= require followable +//= require documentable //= require tree_navigator //= require custom @@ -94,6 +96,7 @@ var initialize_modules = function() { App.LegislationAnnotatable.initialize(); App.WatchFormChanges.initialize(); App.TreeNavigator.initialize(); + App.Documentable.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/documentable.js.coffee b/app/assets/javascripts/documentable.js.coffee new file mode 100644 index 000000000..8683ce5e2 --- /dev/null +++ b/app/assets/javascripts/documentable.js.coffee @@ -0,0 +1,101 @@ +App.Documentable = + + initialize: -> + @initializeDirectUploads() + @initializeInterface() + + initializeDirectUploads: -> + + $('input.document_ajax_attachment[type=file]').fileupload + + paramName: "document[attachment]" + + formData: null + + add: (e, data) -> + wrapper = $(e.target).closest('.document') + index = $(e.target).data('index') + is_nested_document = $(e.target).data('nested-document') + $(wrapper).find('.progress-bar-placeholder').empty() + data.progressBar = $(wrapper).find('.progress-bar-placeholder').html('
') + $(wrapper).find('.progress-bar-placeholder').css('display','block') + data.formData = { + "document[title]": $(wrapper).find('input.document-title').val() || data.files[0].name + "index": index, + "nested_document": is_nested_document + } + data.submit() + + change: (e, data) -> + wrapper = $(e.target).parent() + $.each(data.files, (index, file)-> + $(wrapper).find('.file-name').text(file.name) + ) + + progress: (e, data) -> + progress = parseInt(data.loaded / data.total * 100, 10) + $(data.progressBar).find('.loading-bar').css 'width', progress + '%' + return + + initializeInterface: -> + input_files = $('input.document_ajax_attachment[type=file]') + + $.each input_files, (index, file) -> + wrapper = $(file).parent() + App.Documentable.watchRemoveDocumentbutton(wrapper) + + watchRemoveDocumentbutton: (wrapper) -> + remove_document_button = $(wrapper).find('.remove-document') + $(remove_document_button).on 'click', (e) -> + e.preventDefault() + $(wrapper).remove() + $('#new_document_link').show() + $('.max-documents-notice').hide() + + uploadNestedDocument: (id, nested_document, result) -> + $('#' + id).replaceWith(nested_document) + @updateLoadingBar(id, result) + @initialize() + + uploadPlainDocument: (id, nested_document, result) -> + $('#' + id).replaceWith(nested_document) + @updateLoadingBar(id, result) + @initialize() + + updateLoadingBar: (id, result) -> + if result + $('#' + id).find('.loading-bar').addClass 'complete' + else + $('#' + id).find('.loading-bar').addClass 'errors' + $('#' + id).find('.progress-bar-placeholder').css('display','block') + + new: (nested_fields) -> + $(".documents-list").append(nested_fields) + @initialize() + + destroyNestedDocument: (id, notice) -> + $('#' + id).remove() + @updateNotice(notice) + + replacePlainDocument: (id, notice, plain_document) -> + $('#' + id).replaceWith(plain_document) + @updateNotice(notice) + @initialize() + + updateNotice: (notice) -> + if $('[data-alert]').length > 0 + $('[data-alert]').replaceWith(notice) + else + $("body").append(notice) + + updateNewDocumentButton: (link) -> + if $('.document').length >= $('.documents').data('max-documents') + $('#new_document_link').hide() + $('.max-documents-notice').removeClass('hide') + $('.max-documents-notice').show() + else if $('#new_document_link').length > 0 + $('#new_document_link').replaceWith(link) + $('.max-documents-notice').hide() + else + $('.max-documents-notice').hide() + $(link).insertBefore('.documents hr:last') diff --git a/app/assets/stylesheets/_consul_settings.scss b/app/assets/stylesheets/_consul_settings.scss index 6d0f37559..0844f861f 100644 --- a/app/assets/stylesheets/_consul_settings.scss +++ b/app/assets/stylesheets/_consul_settings.scss @@ -43,6 +43,7 @@ $budget: #7e328a; $budget-hover: #7571bf; $highlight: #e7f2fc; +$highlight-soft: #f3f8fd; $light: #f5f7fa; $featured: #ffdc5c; diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index a14cf3383..6db475365 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -15,3 +15,4 @@ @import 'annotator_overrides'; @import 'jquery-ui/datepicker'; @import 'datepicker_overrides'; +@import 'documentable'; diff --git a/app/assets/stylesheets/documentable.scss b/app/assets/stylesheets/documentable.scss new file mode 100644 index 000000000..2aa015a14 --- /dev/null +++ b/app/assets/stylesheets/documentable.scss @@ -0,0 +1,59 @@ +.progress-bar-placeholder { + display: none; +} + +.document-form { + .document .file-name { + margin-top: 0; + } + .progress-bar-placeholder { + margin-bottom: 15px; + } + .document .loading-bar.errors { + margin-top: $line-height * 2; + } +} + +.document { + .button { + font-weight: normal; + } + + .progress-bar { + width: 100%; + background-color: $light-gray; + } + + input.document_ajax_attachment[type=file]{ + display: none; + } + + .file-name { + margin-top: $line-height / 2; + } + + .loading-bar { + height: 5px; + width: 0; + transition: width 500ms ease-out; + + &.uploading { + background-color: $dark-gray; + } + + &.complete { + background-color: $success-color; + width: 100%; + } + + &.errors { + background-color: $alert-color; + width: 100%; + margin-top: $line-height / 2; + } + } + + .loading-bar.no-transition { + transition: none; + } +} diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss index 782b5cdb2..5e92ee460 100644 --- a/app/assets/stylesheets/icons.scss +++ b/app/assets/stylesheets/icons.scss @@ -97,6 +97,10 @@ content: '\72'; } +.icon-documents::before { + content: '\68'; +} + .icon-proposals::before { content: '\68'; } diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index 7a5705f60..cf07f35ca 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -18,6 +18,7 @@ // 16. Flags // 17. Activity // 18. Banners +// 19. Documents // // 01. Global styles @@ -91,6 +92,11 @@ a { color: $link; } +.button.hollow.error { + border-color: $alert-border; + color: $color-alert; +} + .postfix.button { padding: 0; } @@ -216,9 +222,8 @@ a { } .menu.vertical { - background: #fff; margin: $line-height 0; - padding: $line-height; + padding: $line-height 0; li { margin-bottom: $line-height; @@ -319,6 +324,10 @@ a { background: $brand; } +.align-top { + vertical-align: top; +} + // 02. Header // ---------- @@ -416,6 +425,7 @@ header { .menu { &.is-dropdown-submenu { + background: #fff; margin: 0; margin-top: rem-calc(-24); padding: 0; @@ -1821,6 +1831,7 @@ table { .divider { color: $text-light; + display: inline-block; } form { @@ -2128,3 +2139,125 @@ table { text-decoration: none; } } + +// 19. Documents +.document-form form { + + .radio-buttons { + label { + margin-right: $line-height; + } + } + + .source-option-link { + input { + padding-bottom: 0; + } + + .error { + margin-bottom: $line-height; + } + + label { + &.error { + margin-bottom: 0; + } + } + } + + .source-option-file { + .file-name { + label { + + @include breakpoint(small medium) { + float: none; + } + + @include breakpoint(large) { + float: left; + } + } + + p { + + @include breakpoint(small medium) { + float: none; + margin-top: 0; + margin-left: 0; + margin-bottom: 0; + } + + @include breakpoint(large) { + float: left; + margin-bottom: 0; + margin-top: $line-height / 2; + margin-left: $line-height; + } + } + } + } + + .attachment-errors { + margin-bottom: $line-height; + } +} + +.documents-list { + + table { + border: 0; + } + + td { + position: relative; + + @include breakpoint(small) { + float: left; + width: 100%; + } + + @include breakpoint(medium) { + float: none; + } + + a { + width: 100%; + } + + &:first-child { + padding-left: $line-height * 1.5; + + @include breakpoint(small) { + width: 100%; + } + + @include breakpoint(medium) { + width: 70%; + } + + @include breakpoint(large) { + width: 80%; + } + } + + &:first-child::before { + color: #007bb7; + content: 'G'; + font-family: "icons" !important; + font-size: rem-calc(24); + left: rem-calc(6); + position: absolute; + top: 0; + + @include breakpoint(small) { + padding-top: rem-calc(12); + } + + @include breakpoint(medium) { + padding-top: rem-calc(22); + } + + } + + } +} diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 8af8071ea..8d79eca55 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -248,11 +248,13 @@ .debate-form, .proposal-form, .budget-investment-form, -.spending-proposal-form { +.spending-proposal-form, +.document-form { .icon-debates, .icon-proposals, - .icon-budget { + .icon-budget, + .icon-documents { font-size: rem-calc(50); line-height: $line-height; opacity: 0.5; @@ -262,7 +264,8 @@ color: $debates; } - .icon-proposals { + .icon-proposals, + .icon-documents { color: $proposals; } @@ -294,7 +297,8 @@ } } -.proposal-form { +.proposal-form, +.document-form { .recommendations li::before { color: $proposals; @@ -311,7 +315,8 @@ .polls-show, .debate-quiz, .budget-investment-show, -.draft-panels { +.draft-panels, +.debate-questions { p { word-wrap: break-word; @@ -328,10 +333,13 @@ .whatsapp::before { background-color: #43d854; color: #fff; - font-size: 1.7em; - margin-left: rem-calc(0.5); - padding: rem-calc(9.5) rem-calc(9.8); - vertical-align: rem-calc(10); + display: inline-block; + font-size: rem-calc(30); + height: rem-calc(48); + padding-top: rem-calc(9); + text-align: center; + vertical-align: top; + width: rem-calc(48); } .edit-debate, @@ -424,33 +432,30 @@ .document-link, .video-link { - border: 1px solid $border; + background: $highlight-soft; + border: 1px solid $highlight; display: block; margin: $line-height / 2 0; padding: $line-height / 2; position: relative; a { - padding-left: rem-calc(24); + word-wrap: break-word; } - ::before { - color: #007bb7; - content: 'G'; - font-family: "icons" !important; + [class^="icon-"] { + display: inline-block; font-size: rem-calc(24); - left: rem-calc(6); - padding-top: rem-calc(3); - position: absolute; - top: 0; + line-height: $line-height; + vertical-align: middle; } - } - .video-link { + .icon-document { + color: #007bb7; + } - ::before { + .icon-video { color: #cc181e; - content: 'D'; } } @@ -745,6 +750,12 @@ display: none; } +.document-form{ + max-width: 75rem; + margin-left: auto; + margin-right: auto; +} + .more-info { clear: both; color: $text-medium; @@ -874,6 +885,20 @@ } } +.help-link { + margin-left: $line-height; + position: relative; + + &::before { + color: $link; + content: '\4e'; + font-family: 'icons'; + position: absolute; + left: -24px; + top: -2px; + } +} + // 05. Featured // ------------ @@ -1480,13 +1505,9 @@ .poll, .poll-question { - background: #fff; - border-radius: rem-calc(6); + border: 1px solid $border; margin-bottom: $line-height / 2; -} - -.poll { - padding: $line-height; + padding: $line-height / 2; position: relative; .icon-poll-answer { diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb index 16144cb27..799d1f76d 100644 --- a/app/controllers/budgets/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -44,10 +44,12 @@ module Budgets set_comment_flags(@comment_tree.comments) load_investment_votes(@investment) @investment_ids = [@investment.id] + @document = Document.new(documentable: @investment) end def create @investment.author = current_user + recover_documents_from_cache(@investment) if @investment.save Mailer.budget_investment_created(@investment).deliver_later @@ -104,7 +106,8 @@ module Budgets def investment_params params.require(:budget_investment).permit(:title, :description, :external_url, :heading_id, :tag_list, - :organization_name, :location, :terms_of_service) + :organization_name, :location, :terms_of_service, + documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id]) end def load_ballot diff --git a/app/controllers/concerns/commentable_actions.rb b/app/controllers/concerns/commentable_actions.rb index 62bcdaaac..1c47b0c06 100644 --- a/app/controllers/concerns/commentable_actions.rb +++ b/app/controllers/concerns/commentable_actions.rb @@ -58,6 +58,8 @@ module CommentableActions def update resource.assign_attributes(strong_params) + recover_documents_from_cache(resource) + if resource.save redirect_to resource, notice: t("flash.actions.update.#{resource_name.underscore}") else @@ -110,4 +112,11 @@ module CommentableActions nil end + def recover_documents_from_cache(resource) + return false unless resource.try(:documents) + resource.documents = resource.documents.each do |document| + document.set_attachment_from_cached_attachment if document.cached_attachment.present? + end + end + end diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb new file mode 100644 index 000000000..ceb7d191f --- /dev/null +++ b/app/controllers/documents_controller.rb @@ -0,0 +1,100 @@ +class DocumentsController < ApplicationController + before_action :authenticate_user! + before_filter :find_documentable, except: :destroy + before_filter :prepare_new_document, only: [:new, :new_nested] + before_filter :prepare_document_for_creation, only: :create + + load_and_authorize_resource except: :upload + skip_authorization_check only: :upload + + def new + end + + def new_nested + end + + def create + recover_attachments_from_cache + + if @document.save + flash[:notice] = t "documents.actions.create.notice" + redirect_to params[:from] + else + flash[:alert] = t "documents.actions.create.alert" + render :new + end + end + + def destroy + respond_to do |format| + format.html do + if @document.destroy + flash[:notice] = t "documents.actions.destroy.notice" + else + flash[:alert] = t "documents.actions.destroy.alert" + end + redirect_to params[:from] + end + format.js do + if @document.destroy + flash.now[:notice] = t "documents.actions.destroy.notice" + else + flash.now[:alert] = t "documents.actions.destroy.alert" + end + end + end + end + + def destroy_upload + @document = Document.new(cached_attachment: params[:path]) + @document.set_attachment_from_cached_attachment + @document.documentable = @documentable + + if @document.attachment.destroy + flash.now[:notice] = t "documents.actions.destroy.notice" + else + flash.now[:alert] = t "documents.actions.destroy.alert" + end + render :destroy + end + + def upload + @document = Document.new(document_params.merge(user: current_user)) + @document.documentable = @documentable + + if @document.valid? + @document.attachment.save + @document.set_cached_attachment_from_attachment(URI(request.url)) + else + @document.attachment.destroy + end + end + + private + + def document_params + params.require(:document).permit(:title, :documentable_type, :documentable_id, + :attachment, :cached_attachment, :user_id) + end + + def find_documentable + @documentable = params[:documentable_type].constantize.find_or_initialize_by(id: params[:documentable_id]) + end + + def prepare_new_document + @document = Document.new(documentable: @documentable, user_id: current_user.id) + end + + def prepare_document_for_creation + @document = Document.new(document_params) + @document.documentable = @documentable + @document.user = current_user + end + + def recover_attachments_from_cache + if @document.attachment.blank? && @document.cached_attachment.present? + @document.set_attachment_from_cached_attachment + end + end + +end diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 8b75a10cf..cedc4e8ef 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -19,11 +19,13 @@ class ProposalsController < ApplicationController def show super @notifications = @proposal.notifications + @document = Document.new(documentable: @proposal) redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal) end def create @proposal = Proposal.new(proposal_params.merge(author: current_user)) + recover_documents_from_cache(@proposal) if @proposal.save redirect_to share_proposal_path(@proposal), notice: I18n.t('flash.actions.create.proposal') @@ -75,7 +77,8 @@ class ProposalsController < ApplicationController def proposal_params params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url, - :responsible_name, :tag_list, :terms_of_service, :geozone_id) + :responsible_name, :tag_list, :terms_of_service, :geozone_id, + documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id] ) end def retired_params @@ -121,4 +124,5 @@ class ProposalsController < ApplicationController def load_successful_proposals @proposal_successful_exists = Proposal.successful.exists? end + end diff --git a/app/helpers/documentables_helper.rb b/app/helpers/documentables_helper.rb new file mode 100644 index 000000000..4fd737908 --- /dev/null +++ b/app/helpers/documentables_helper.rb @@ -0,0 +1,41 @@ +module DocumentablesHelper + + def documentable_class(documentable) + documentable.class.name.parameterize('_') + end + + def max_documents_allowed(documentable) + documentable.class.max_documents_allowed + end + + def max_file_size(documentable) + bytesToMeg(documentable.class.max_file_size) + end + + def accepted_content_types(documentable) + documentable.class.accepted_content_types + end + + def accepted_content_types_extensions(documentable_class) + documentable_class.accepted_content_types + .collect{ |content_type| ".#{content_type.split("/").last}" } + .join(",") + end + + def humanized_accepted_content_types(documentable) + documentable.class.accepted_content_types + .collect{ |content_type| content_type.split("/").last } + .join(", ") + end + + def documentables_note(documentable) + t "documents.form.note", max_documents_allowed: max_documents_allowed(documentable), + accepted_content_types: humanized_accepted_content_types(documentable), + max_file_size: max_file_size(documentable) + end + + def max_documents_allowed?(documentable) + documentable.documents.count >= documentable.class.max_documents_allowed + end + +end \ No newline at end of file diff --git a/app/helpers/documents_helper.rb b/app/helpers/documents_helper.rb new file mode 100644 index 000000000..17d70068b --- /dev/null +++ b/app/helpers/documents_helper.rb @@ -0,0 +1,89 @@ +module DocumentsHelper + + def document_attachment_file_name(document) + document.attachment_file_name + end + + def errors_on_attachment(document) + document.errors[:attachment].join(', ') if document.errors.key?(:attachment) + end + + def bytesToMeg(bytes) + bytes / Numeric::MEGABYTE + end + + def document_nested_field_name(document, index, field) + parent = document.documentable_type.parameterize.underscore + "#{parent.parameterize}[documents_attributes][#{index}][#{field}]" + end + + def document_nested_field_id(document, index, field) + parent = document.documentable_type.parameterize.underscore + "#{parent.parameterize}_documents_attributes_#{index}_#{field}" + end + + def document_nested_field_wrapper_id(index) + "document_#{index}" + end + + def render_destroy_document_link(document, index) + if document.persisted? + link_to t('documents.form.delete_button'), + document_path(document, index: index, nested_document: true), + method: :delete, + remote: true, + data: { confirm: t('documents.actions.destroy.confirm') }, + class: "delete float-right" + elsif !document.persisted? && document.cached_attachment.present? + link_to t('documents.form.delete_button'), + destroy_upload_documents_path(path: document.cached_attachment, + nested_document: true, + index: index, + documentable_type: document.documentable_type, + documentable_id: document.documentable_id), + method: :delete, + remote: true, + class: "delete float-right" + else + link_to t('documents.form.delete_button'), + "#", + class: "delete float-right remove-document" + end + end + + def render_attachment(document, index) + html = file_field_tag :attachment, + accept: accepted_content_types_extensions(document.documentable_type.constantize), + class: 'document_ajax_attachment', + data: { + url: document_direct_upload_url(document), + cached_attachment_input_field: document_nested_field_id(document, index, :cached_attachment), + multiple: false, + index: index, + nested_document: true + }, + name: document_nested_field_name(document, index, :attachment), + id: document_nested_field_id(document, index, :attachment) + if document.attachment.blank? && document.cached_attachment.blank? + klass = document.errors[:attachment].any? ? "error" : "" + html += label_tag document_nested_field_id(document, index, :attachment), + t("documents.form.attachment_label"), + class: "button hollow #{klass}" + if document.errors[:attachment].any? + html += content_tag :small, class: "error" do + errors_on_attachment(document) + end + end + end + html + end + + def document_direct_upload_url(document) + upload_documents_url( + documentable_type: document.documentable_type, + documentable_id: document.documentable_id, + format: :js + ) + end + +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index e7aeb5a21..111a80267 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -40,6 +40,26 @@ module UsersHelper current_user && current_user.administrator? end + def current_moderator? + current_user && current_user.moderator? + end + + def current_valuator? + current_user && current_user.valuator? + end + + def current_manager? + current_user && current_user.manager? + end + + def current_poll_officer? + current_user && current_user.poll_officer? + end + + def show_admin_menu? + current_administrator? || current_moderator? || current_valuator? || current_manager? || current_poll_officer? + end + def interests_title_text(user) if current_user == user t('account.show.public_interests_my_title_list') diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index bc2fea5d4..b8f80c0cd 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -73,6 +73,7 @@ module Abilities can [:manage], ::Legislation::Question cannot :comment_as_moderator, [::Legislation::Question, Legislation::Annotation] + can [:create, :destroy], Document end end end diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 33a9d50f6..311b0dade 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -36,6 +36,9 @@ module Abilities can [:create, :destroy], Follow + can [:create, :destroy, :new], Document, documentable: { author_id: user.id } + can [:new_nested, :upload, :destroy_upload], Document + unless user.organization? can :vote, Debate can :vote, Comment diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index a5bea4508..0dfd7836c 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -1,12 +1,16 @@ class Budget class Investment < ActiveRecord::Base - include Measurable include Sanitizable include Taggable include Searchable include Reclassification include Followable + include Documentable + documentable max_documents_allowed: 3, + max_file_size: 3.megabytes, + accepted_content_types: [ "application/pdf" ] + accepts_nested_attributes_for :documents, allow_destroy: true acts_as_votable acts_as_paranoid column: :hidden_at diff --git a/app/models/concerns/documentable.rb b/app/models/concerns/documentable.rb new file mode 100644 index 000000000..4aeaf6eab --- /dev/null +++ b/app/models/concerns/documentable.rb @@ -0,0 +1,20 @@ +module Documentable + extend ActiveSupport::Concern + + included do + has_many :documents, as: :documentable, dependent: :destroy + end + + module ClassMethods + attr_reader :max_documents_allowed, :max_file_size, :accepted_content_types + + private + + def documentable(options= {}) + @max_documents_allowed = options[:max_documents_allowed] + @max_file_size = options[:max_file_size] + @accepted_content_types = options[:accepted_content_types] + end + end + +end diff --git a/app/models/document.rb b/app/models/document.rb new file mode 100644 index 000000000..7fd82ea33 --- /dev/null +++ b/app/models/document.rb @@ -0,0 +1,81 @@ +class Document < ActiveRecord::Base + include DocumentsHelper + include DocumentablesHelper + has_attached_file :attachment, path: ":rails_root/public/system/:class/:prefix/:style/:filename" + attr_accessor :cached_attachment + + belongs_to :user + belongs_to :documentable, polymorphic: true + + # Disable paperclip security validation due to polymorphic configuration + # Paperclip do not allow to user Procs on valiations definition + do_not_validate_attachment_file_type :attachment + validate :attachment_presence + validate :validate_attachment_content_type, if: -> { attachment.present? } + validate :validate_attachment_size, if: -> { attachment.present? } + validates :title, presence: true + validates :user_id, presence: true + validates :documentable_id, presence: true, if: -> { persisted? } + validates :documentable_type, presence: true, if: -> { persisted? } + + after_save :remove_cached_document, if: -> { valid? && persisted? && cached_attachment.present? } + + def set_cached_attachment_from_attachment(prefix) + self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem + attachment.path + else + prefix + attachment.url + end + end + + def set_attachment_from_cached_attachment + self.attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem + File.open(cached_attachment) + else + URI.parse(cached_attachment) + end + end + + Paperclip.interpolates :prefix do |attachment, style| + attachment.instance.prefix(attachment, style) + end + + def prefix(attachment, style) + if !attachment.instance.persisted? + "cached_attachments/user/#{attachment.instance.user_id}" + else + ":attachment/:id_partition" + end + end + + private + + def validate_attachment_size + if documentable.present? && + attachment_file_size > documentable.class.max_file_size + errors[:attachment] = I18n.t("documents.errors.messages.in_between", + min: "0 Bytes", + max: "#{max_file_size(documentable)} MB") + end + end + + def validate_attachment_content_type + if documentable.present? && + !accepted_content_types(documentable).include?(attachment_content_type) + errors[:attachment] = I18n.t("documents.errors.messages.wrong_content_type", + content_type: attachment_content_type, + accepted_content_types: humanized_accepted_content_types(documentable)) + end + end + + def attachment_presence + if attachment.blank? && cached_attachment.blank? + errors[:attachment] = I18n.t("errors.messages.blank") + end + end + + def remove_cached_document + File.delete(cached_attachment) if File.exists?(cached_attachment) + end + +end diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 21335c5a5..000c2d42d 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -9,6 +9,11 @@ class Proposal < ActiveRecord::Base include HasPublicAuthor include Graphqlable include Followable + include Documentable + documentable max_documents_allowed: 3, + max_file_size: 3.megabytes, + accepted_content_types: [ "application/pdf" ] + accepts_nested_attributes_for :documents, allow_destroy: true acts_as_votable acts_as_paranoid column: :hidden_at diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb index 9f5ab0ab2..494568706 100644 --- a/app/views/budgets/index.html.erb +++ b/app/views/budgets/index.html.erb @@ -1,14 +1,9 @@ +<% provide :title do %><%= t('budgets.index.title') %><% end %> <% content_for :canonical do %> <%= render "shared/canonical", href: budgets_url %> <% end %> -
-
-
-

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

-
-
-
+<%= render "shared/section_header", i18n_namespace: "budgets.index.section_header", image: "budgets" %>
@@ -32,5 +27,16 @@ <% end %> + +
+

+ <%= t("budgets.index.section_footer.title") %> +

+

<%= t("budgets.index.section_footer.help_text_1") %>

+

<%= t("budgets.index.section_footer.help_text_2") %>

+

<%= t("budgets.index.section_footer.help_text_3", + org: link_to(setting['org_name'], new_user_registration_path)).html_safe %>

+

<%= t("budgets.index.section_footer.help_text_4") %>

+
diff --git a/app/views/budgets/investments/_filter_subnav.html.erb b/app/views/budgets/investments/_filter_subnav.html.erb index a46c33b2d..01fef4657 100644 --- a/app/views/budgets/investments/_filter_subnav.html.erb +++ b/app/views/budgets/investments/_filter_subnav.html.erb @@ -17,6 +17,14 @@ <% end %> +
  • + <%= link_to "#tab-documents" do %> +

    + <%= t("documents.tab") %> + (<%= @investment.documents.count %>) +

    + <% end %> +
  • diff --git a/app/views/budgets/investments/_form.html.erb b/app/views/budgets/investments/_form.html.erb index 94f6283e9..7cf2bb1e2 100644 --- a/app/views/budgets/investments/_form.html.erb +++ b/app/views/budgets/investments/_form.html.erb @@ -21,6 +21,10 @@ <%= f.text_field :external_url %> +
    + <%= render 'documents/nested_documents', documentable: @investment %> +
    +
    <%= f.text_field :location %>
    diff --git a/app/views/budgets/investments/_investment_show.html.erb b/app/views/budgets/investments/_investment_show.html.erb index 434934c7e..3f1890ae8 100644 --- a/app/views/budgets/investments/_investment_show.html.erb +++ b/app/views/budgets/investments/_investment_show.html.erb @@ -4,6 +4,12 @@
    <%= back_link_to budget_investments_path(investment.budget, heading_id: investment.heading) %> + <% if can?(:create, @document) && investment.documents.size < Budget::Investment.max_documents_allowed %> + <%= link_to t("documents.upload_document"), + new_document_path(documentable_id:investment, documentable_type: investment.class.name, from: request.url), + class: 'button hollow float-right' %> + <% end %> +

    <%= investment.title %>

    @@ -14,7 +20,6 @@  •  <%= investment.heading.name %>
    -

    <%= t("budgets.investments.show.code_html", code: investment.id) %> @@ -51,6 +56,7 @@

    <%= t('budgets.investments.show.price_explanation') %>

    <%= investment.price_explanation %>

    <% end %> +