diff --git a/CHANGELOG.md b/CHANGELOG.md index be57f2cfe..57ebfbbb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ - [TODO DEPLOY] add the `USER_CONFIRMATION_NEEDED_TO_SIGN_IN` environment variable (see [doc/environment.md](doc/environment.md#USER_CONFIRMATION_NEEDED_TO_SIGN_IN) for configuration details) - [TODO DEPLOY] -> (only dev) `bundle install && yarn install` - [TODO DEPLOY] `rake db:migrate && rake db:seed` +- [TODO DEPLOY] `rake fablab:fix:name_stylesheet` ## v4.2.4 2019 October 30 diff --git a/app/assets/javascripts/app.js b/app/assets/javascripts/app.js index 102b037d4..7cf00c581 100644 --- a/app/assets/javascripts/app.js +++ b/app/assets/javascripts/app.js @@ -20,7 +20,7 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout 'ui.select', 'ui.calendar', 'angularMoment', 'Devise', 'DeviseModal', 'angular-growl', 'xeditable', 'checklist-model', 'unsavedChanges', 'angular-loading-bar', 'ngTouch', 'angular-google-analytics', 'angularUtils.directives.dirDisqus', 'summernote', 'elasticsearch', 'angular-medium-editor', 'naif.base64', - 'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock', 'vcRecaptcha']) + 'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock', 'vcRecaptcha', 'ui.codemirror']) .config(['$httpProvider', 'AuthProvider', 'growlProvider', 'unsavedWarningsConfigProvider', 'AnalyticsProvider', 'uibDatepickerPopupConfig', '$provide', '$translateProvider', function ($httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, uibDatepickerPopupConfig, $provide, $translateProvider) { // Google analytics diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index de7041dc7..ceba90d3a 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -67,6 +67,11 @@ //= require angular-aside/dist/js/angular-aside //= require ng-caps-lock/ng-caps-lock //= require angular-recaptcha +//= require codemirror/lib/codemirror +//= require codemirror/addon/edit/matchbrackets +//= require codemirror/mode/css/css +//= require codemirror/mode/sass/sass +//= require angular-ui-codemirror/src/ui-codemirror //= require_tree ./controllers //= require_tree ./services //= require_tree ./directives diff --git a/app/assets/javascripts/controllers/admin/settings.js.erb b/app/assets/javascripts/controllers/admin/settings.js.erb index 2c7091f78..55627ab4f 100644 --- a/app/assets/javascripts/controllers/admin/settings.js.erb +++ b/app/assets/javascripts/controllers/admin/settings.js.erb @@ -60,6 +60,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope' $scope.aboutContactsSetting = { name: 'about_contacts', value: settingsPromise.about_contacts }; $scope.homeBlogpostSetting = { name: 'home_blogpost', value: settingsPromise.home_blogpost }; $scope.homeContent = { name: 'home_content', value: settingsPromise.home_content }; + $scope.homeCss = { name: 'home_css', value: settingsPromise.home_css }; $scope.machineExplicationsAlert = { name: 'machine_explications_alert', value: settingsPromise.machine_explications_alert }; $scope.trainingExplicationsAlert = { name: 'training_explications_alert', value: settingsPromise.training_explications_alert }; $scope.trainingInformationMessage = { name: 'training_information_message', value: settingsPromise.training_information_message }; @@ -151,6 +152,21 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope' ] } + // codemirror editor + $scope.codeMirrorEditor = null; + + // Options for codemirror editor, used for custom css + $scope.codemirrorOpts = { + matchBrackets : true, + lineNumbers: true, + mode: 'sass' + } + + // Show or hide advanced settings + $scope.advancedSettings = { + open: false + } + /** * For use with 'ng-class', returns the CSS class name for the uploads previews. * The preview may show a placeholder or the content of the file depending on the upload state. @@ -332,6 +348,14 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope' ) } + /** + * Callback triggered when the codemirror editor is loaded into the DOM + * @param editor codemirror instance + */ + $scope.codemirrorLoaded = function (editor) { + $scope.codeMirrorEditor = editor; + } + /* PRIVATE SCOPE */ /** @@ -377,6 +401,11 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope' privacyDraftsPromise.setting.history.forEach(function (draft) { $scope.privacyDraftsHistory.push({ id: draft.id, name: _t('app.admin.settings.privacy.draft_from_USER_DATE', { USER: draft.user.name, DATE: moment(draft.created_at).format('L LT') }), content: draft.value }); }); + + // refresh codemirror to display the fetched setting + $scope.$watch('advancedSettings.open', function (newValue) { + if (newValue) $scope.codeMirrorEditor.refresh(); + }) }; // init the controller (call at the end !) diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 017524a33..ff921a190 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -1002,7 +1002,7 @@ angular.module('application.router', ['ui.router']) 'fablab_name', 'name_genre', 'reminder_enable', \ 'reminder_delay', 'visibility_yearly', 'visibility_others', \ 'display_name_enable', 'machines_sort_by', 'fab_analytics', \ - 'link_name', 'home_content']` }).$promise; + 'link_name', 'home_content', 'home_css']` }).$promise; }], privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }], cguFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgu-file' }).$promise; }], diff --git a/app/assets/stylesheets/application.scss.erb b/app/assets/stylesheets/application.scss.erb index b87230fd9..96b307415 100644 --- a/app/assets/stylesheets/application.scss.erb +++ b/app/assets/stylesheets/application.scss.erb @@ -14,6 +14,7 @@ *= require summernote/dist/summernote *= require jquery-minicolors/jquery.minicolors.css *= require angular-aside/dist/css/angular-aside + *= require codemirror/lib/codemirror */ @import "app.functions"; diff --git a/app/assets/stylesheets/modules/abuses.scss b/app/assets/stylesheets/modules/abuses.scss index 300ba1922..fbd61f222 100644 --- a/app/assets/stylesheets/modules/abuses.scss +++ b/app/assets/stylesheets/modules/abuses.scss @@ -28,4 +28,4 @@ li.abuse { margin-top: 1em; } } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/modules/settings.scss b/app/assets/stylesheets/modules/settings.scss index fccd4ee60..2536f61d2 100644 --- a/app/assets/stylesheets/modules/settings.scss +++ b/app/assets/stylesheets/modules/settings.scss @@ -40,5 +40,24 @@ } } } + .home-page-style { + .panel { + border: 0; + .panel-heading { + background: none; + + .panel-title { + font-size: 12px; + font-style: italic; + } + } + + .CodeMirror { + border: 1px solid #ddd; + font-size: 12px; + height: 400px; + } + } + } } } diff --git a/app/assets/templates/admin/settings/home_page.html b/app/assets/templates/admin/settings/home_page.html index ef55538c1..6fdadb330 100644 --- a/app/assets/templates/admin/settings/home_page.html +++ b/app/assets/templates/admin/settings/home_page.html @@ -36,5 +36,27 @@ +
+ + + + {{ 'app.admin.settings.advanced' }} + +
+

{{ 'app.admin.settings.customize_home_page_css' }}

+
+
+ +

+ + {{ 'app.admin.settings.home_css_notice' }} +

+
+ +
+
+
+
diff --git a/app/controllers/api/settings_controller.rb b/app/controllers/api/settings_controller.rb index 2aad40349..9faaf350a 100644 --- a/app/controllers/api/settings_controller.rb +++ b/app/controllers/api/settings_controller.rb @@ -12,6 +12,7 @@ class API::SettingsController < API::ApiController authorize Setting @setting = Setting.find_or_initialize_by(name: params[:name]) if @setting.save && @setting.history_values.create(value: setting_params[:value], invoicing_profile: current_user.invoicing_profile) + SettingService.new.after_update(@setting) render status: :ok else render json: @setting.errors.full_messages, status: :unprocessable_entity diff --git a/app/models/custom_asset.rb b/app/models/custom_asset.rb index a0e0b22a4..1b129b826 100644 --- a/app/models/custom_asset.rb +++ b/app/models/custom_asset.rb @@ -1,3 +1,6 @@ +# frozen_string_literal: true + +# Admin defined assets (like PDF or images uploaded) class CustomAsset < ActiveRecord::Base has_one :custom_asset_file, as: :viewable, dependent: :destroy accepts_nested_attributes_for :custom_asset_file, allow_destroy: true @@ -11,6 +14,6 @@ class CustomAsset < ActiveRecord::Base after_update :update_stylesheet if :viewable_changed? def update_stylesheet - Stylesheet.first.rebuild! if %w[profile-image-file].include? name + Stylesheet.theme.rebuild! if %w[profile-image-file].include? name end end diff --git a/app/models/setting.rb b/app/models/setting.rb index cd9506dad..2fe9e39a0 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -3,6 +3,7 @@ # Setting is a configuration element of the platform. Only administrators are allowed to modify Settings # For some settings, changing them will involve some callback actions (like rebuilding the stylesheets if the theme color Setting is changed). # A full history of the previous values is kept in database with the date and the author of the change +# after_update callback is handled by SettingService class Setting < ActiveRecord::Base has_many :history_values validates :name, inclusion: @@ -71,20 +72,8 @@ class Setting < ActiveRecord::Base hub_public_key fab_analytics link_name - home_content] } - - after_update :update_stylesheet, :notify_privacy_policy_changed if :value_changed? - - def update_stylesheet - Stylesheet.first&.rebuild! if %w[main_color secondary_color].include? name - end - - def notify_privacy_policy_changed - return unless name == 'privacy_body' - - NotifyPrivacyUpdateWorker.perform_async(id) - end - + home_content + home_css] } def value last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).first last_value&.value diff --git a/app/models/stylesheet.rb b/app/models/stylesheet.rb index 193b37581..4582964f5 100644 --- a/app/models/stylesheet.rb +++ b/app/models/stylesheet.rb @@ -6,19 +6,23 @@ class Stylesheet < ActiveRecord::Base validates_presence_of :contents - def rebuild! - return unless Stylesheet.primary && Stylesheet.secondary + ## ===== THEME ===== - update(contents: Stylesheet.css) + def rebuild! + if Stylesheet.primary && Stylesheet.secondary && name == 'theme' + update(contents: Stylesheet.css) + elsif name == 'home_page' + update(contents: Stylesheet.home_page_css) + end end def self.build_sheet! return unless Stylesheet.primary && Stylesheet.secondary - if Stylesheet.first - Stylesheet.first.rebuild! + if Stylesheet.theme + Stylesheet.theme.rebuild! else - Stylesheet.create!(contents: Stylesheet.css) + Stylesheet.create!(contents: Stylesheet.css, name: 'theme') end end @@ -50,6 +54,10 @@ class Stylesheet < ActiveRecord::Base Stylesheet.primary.paint.to_rgb.insert(3, 'a').insert(-2, ", #{alpha}") end + def self.theme + Stylesheet.find_by(name: 'theme') + end + def self.css # rubocop:disable Metrics/AbcSize <<~CSS .bg-red { background-color: #{Stylesheet.primary}; } @@ -91,4 +99,28 @@ class Stylesheet < ActiveRecord::Base section#cookies-modal div.cookies-consent .cookies-actions button.accept { background-color: #{Stylesheet.secondary}; } CSS end + + ## ===== HOME PAGE ===== + + def self.home_style + style = Setting.find_by(name: 'home_css')&.value + ".home-page { #{style} }" + end + + def self.build_home! + if Stylesheet.home_page + Stylesheet.home_page.rebuild! + else + Stylesheet.create!(contents: Stylesheet.home_page_css, name: 'home_page') + end + end + + def self.home_page + Stylesheet.find_by(name: 'home_page') + end + + def self.home_page_css + engine = Sass::Engine.new(home_style, syntax: :scss) + engine.render.presence || '.home-page {}' + end end diff --git a/app/services/setting_service.rb b/app/services/setting_service.rb new file mode 100644 index 000000000..9e54bc04b --- /dev/null +++ b/app/services/setting_service.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Settings are saved in two database tables: Settings and HistoryValues. +# Due to the way the controller updates the settings, we cannot safely use ActiveRecord's callbacks (eg. after_update, after_commit...) +# so this service provides a wrapper around these operations. +class SettingService + def after_update(setting) + # update the stylesheet + Stylesheet.theme&.rebuild! if %w[main_color secondary_color].include? setting.name + Stylesheet.home_page&.rebuild! if setting.name == 'home_css' + + # notify about a change in privacy policy + NotifyPrivacyUpdateWorker.perform_async(id) if setting.name == 'privacy_body' + end +end diff --git a/app/sweepers/stylesheet_sweeper.rb b/app/sweepers/stylesheet_sweeper.rb index 2c05fab8f..689e8f2a3 100644 --- a/app/sweepers/stylesheet_sweeper.rb +++ b/app/sweepers/stylesheet_sweeper.rb @@ -1,9 +1,10 @@ +# frozen_string_literal: true + +# Build a cached version of the CSS stylesheet class StylesheetSweeper < ActionController::Caching::Sweeper observe Stylesheet def after_update(record) - if record.contents_changed? - expire_page(:controller => 'stylesheets', action: 'show', id: record.id) - end + expire_page(controller: 'stylesheets', action: 'show', id: record.id) if record.contents_changed? end -end \ No newline at end of file +end diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index e3754226d..b11337511 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -65,9 +65,12 @@ <%= stylesheet_link_tag 'application', media: 'all' %> <%= stylesheet_link_tag 'app.printer', media: 'print' %> - <% if !Stylesheet.first.nil? %> - - <% end %> + <% unless Stylesheet.theme.nil? %> + + <% end %> + <% unless Stylesheet.home_page.nil? %> + + <% end %> <% if CustomAsset.get_url('favicon-file') %> diff --git a/config/database.yml.default b/config/database.yml.default index a33ae7da3..55839e171 100644 --- a/config/database.yml.default +++ b/config/database.yml.default @@ -1,3 +1,6 @@ +# For development & test environments, copy this file to database.yml +# For staging & production environments, use docker/database.yml + development: adapter: postgresql host: localhost @@ -18,22 +21,3 @@ test: pool: 25 username: postgres password: postgres - -staging: - adapter: postgresql - host: localhost - encoding: unicode - database: fabmanager_development - pool: 25 - username: postgres - password: postgres - - -production: - adapter: postgresql - host: localhost - encoding: unicode - database: fabmanager_development - pool: 25 - username: postgres - password: postgres diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 10629b518..abac07774 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -940,6 +940,7 @@ fr: item_events: "Prochains événements" home_content: "la page d'accueil" home_content_reset: "La page d'accueil a bien été restaurée dans sa configuration initiale." + home_css: "la feuille de style de la page d'accueil" home_blogpost: "la brève de la page d'accueil" twitter_name: "nom du flux Twitter" link_name: "l'intitulé du lien vers la page \"À propos\"" @@ -969,6 +970,9 @@ fr: elements_ordering: "Ordre d'affichage des éléments" machines_order: "Ordre des machines" display_machines_sorted_by: "Afficher les machines triées par" + advanced: "Paramètres avancés" + customize_home_page_css: "Personnaliser la feuille de style de la page d'accueil" + home_css_notice: "Vous pouvez utiliser la syntaxe SASS. Pas de garantie que ça va pas péter avec les updates" sort_by: default: "Défaut" name: "Nom" diff --git a/db/migrate/20200127111404_add_name_to_stylesheet.rb b/db/migrate/20200127111404_add_name_to_stylesheet.rb new file mode 100644 index 000000000..57797c7f2 --- /dev/null +++ b/db/migrate/20200127111404_add_name_to_stylesheet.rb @@ -0,0 +1,5 @@ +class AddNameToStylesheet < ActiveRecord::Migration + def change + add_column :stylesheets, :name, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 1d370074c..286c27881 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20191202135507) do +ActiveRecord::Schema.define(version: 20200127111404) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -760,6 +760,7 @@ ActiveRecord::Schema.define(version: 20191202135507) do t.text "contents" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "name" end create_table "subscriptions", force: :cascade do |t| diff --git a/db/seeds.rb b/db/seeds.rb index 179a875f6..15f049f9f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -427,6 +427,7 @@ unless Setting.find_by(name: 'secondary_color').try(:value) end Stylesheet.build_sheet! +Stylesheet.build_home! unless Setting.find_by(name: 'training_information_message').try(:value) setting = Setting.find_or_initialize_by(name: 'training_information_message') diff --git a/docker/database.yml b/docker/database.yml index 4feff4ea1..ab1dcb5c0 100644 --- a/docker/database.yml +++ b/docker/database.yml @@ -1,3 +1,6 @@ +# For staging & production environments, copy this file to database.yml +# For development & test environments, you can use this file OR config/database.yml.default + default: &default adapter: postgresql encoding: unicode diff --git a/lib/tasks/fablab/fix.rake b/lib/tasks/fablab/fix.rake index 32453807e..e8c06892c 100644 --- a/lib/tasks/fablab/fix.rake +++ b/lib/tasks/fablab/fix.rake @@ -143,5 +143,12 @@ namespace :fablab do attached_object: u end end + + desc '[release 4.3.0] add name to theme stylesheet' + task name_stylesheet: :environment do + Stylesheet.order(:created_at).first.update_attributes( + name: 'theme' + ) + end end end diff --git a/package.json b/package.json index 4f6db971f..a0a2153c3 100644 --- a/package.json +++ b/package.json @@ -51,10 +51,12 @@ "angular-translate-loader-partial": "2.18", "angular-ui-bootstrap": "0.14.3", "angular-ui-calendar": "https://github.com/angular-ui/ui-calendar.git#1.0.1", + "angular-ui-codemirror": "^0.3.0", "angular-unsavedchanges": "0.2", "angular-xeditable": "0.10", "bootstrap-switch": "3.3.2", "checklist-model": "0.2", + "codemirror": "^4.8.0", "d3": "3.5", "elasticsearch-browser": "3.1", "font-awesome": "4.3.0", diff --git a/yarn.lock b/yarn.lock index 570b919b1..861569af7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -182,6 +182,11 @@ angular-ui-bootstrap@0.14.3: version "1.0.1" resolved "https://github.com/angular-ui/ui-calendar.git#f0ab8e186da6b946eafa12ab4154578c1fdf1e1d" +angular-ui-codemirror@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/angular-ui-codemirror/-/angular-ui-codemirror-0.3.0.tgz#e4286fc50f393f2a6e697c1bf3170424e12cbb60" + integrity sha1-5ChvxQ85PypuaXwb8xcEJOEsu2A= + angular-unsavedchanges@0.2: version "0.2.5" resolved "https://registry.yarnpkg.com/angular-unsavedchanges/-/angular-unsavedchanges-0.2.5.tgz#34be961d547051f2c6d536c60a5b42f565c8858b" @@ -301,6 +306,11 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +codemirror@^4.8.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-4.13.0.tgz#209772d38a7bb99647c37b500db121110dd9af6f" + integrity sha1-IJdy04p7uZZHw3tQDbEhEQ3Zr28= + coffee-script@~1.7.0: version "1.7.1" resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.7.1.tgz#62996a861780c75e6d5069d13822723b73404bfc"