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"