app/views/admin/communication/blocks/edit.html.erb
<% content_for :title, @block %>
<% content_for :title_right do %>
<%= t "enums.communication.block.template_kind.#{@block.template_kind}" %>
<% end %>
<div id="app"
class="pb-5"
v-cloak
data-languagetool-locale="<%= I18n.locale %>"
data-languagetool-email="<%= ENV['LANGUAGE_TOOL_USERNAME'] %>"
data-languagetool-token="<%= ENV['LANGUAGE_TOOL_ADD_ON_TOKEN'] %>">
<div class="spinner-border text-primary" role="status">
<span class="sr-only"><%= t 'loading' %></span>
</div>
<%= simple_form_for [:admin, @block],
remote: true,
html: { id: 'block-form' } do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
<%= render 'admin/application/a11y/widget', about: @block %>
<div class="row">
<div class="col-md-9">
<%= f.input :title,
input_html: { data: { translatable: true } } %>
</div>
<div class="col-md-3">
<label class="form-label d-none d-md-block"> </label>
<%= f.input :published, wrapper: :custom_boolean_switch %>
</div>
</div>
<%= render "admin/communication/blocks/templates/#{@block.template_kind}/edit", f: f, block: @block %>
<div>
<a class="text-muted small"
data-bs-toggle="collapse"
href="#advanced"
role="button"
aria-expanded="false"
aria-controls="advanced">
<i class="bi bi-gear-fill"></i>
<%= t('admin.advanced_settings') %>
</a>
<div class="collapse mt-3" id="advanced">
<%
if @block.title.present?
slug = @block.slug
about_l10n = @block.about
about = about_l10n.about
%>
<div class="mb-5">
<%= osuny_label t('admin.communication.blocks.advanced.anchor') %>
<pre>#<%= slug %></pre>
<%
if about.is_direct_object?
website = about.website
path = about_l10n.current_permalink_in_website(website)&.path
%>
<%= osuny_label t('admin.communication.blocks.advanced.url_with_anchor') %>
<pre><%= path %>#<%= slug %></pre>
<% end %>
</div>
<% end %>
<%= f.input :html_class %>
<% if @block.html_class.present? %>
<pre><%= @block.html_class_prepared %></pre>
<% end %>
</div>
</div>
<textarea name="communication_block[data]" rows="20" cols="200" class="d-none">
{{ JSON.stringify(data) }}
</textarea>
<% content_for :action_bar_left do %>
<%= render 'admin/application/l10n/libre_translate_button', l10n: @block.about %>
<% end %>
<% content_for :action_bar_right do %>
<%= submit f %>
<% end %>
<% end %>
</div>
<%# Include vue.js before call Vue.createApp %>
<%= javascript_include_tag 'vue' %>
<script nonce="<%= request.content_security_policy_nonce %>">
var app = Vue.createApp({
components: {
draggable: VueDraggableNext.VueDraggableNext,
},
data() {
return {
directUpload: {
url: "<%= rails_direct_uploads_url.html_safe %>",
blobUrlTemplate: "<%= medium_url(":signed_id", ":filename").html_safe %>"
},
data: <%= @block.data.to_json.html_safe %>,
<% if @element %>
defaultElement: <%= @element.default_data.to_json.html_safe %>
<% end %>
}
},
methods: {
// TODO : create a uploader vue3 component
onMultipleFileImageChange(event, key) {
var files = event.target.files || event.dataTransfer.files;
if (!files.length)
return;
var index = 0;
for(var i = 0; i < files.length; i += 1) {
index = this.data.elements.length;
this.data.elements.push(JSON.parse(JSON.stringify(this.defaultElement)));
this.uploadFile(event.target, files[i], this.data.elements[index], key);
}
},
onFileImageChange(event, object, key) {
var files = event.target.files || event.dataTransfer.files;
if (!files.length)
return;
this.uploadFile(event.target, files[0], object, key);
},
uploadFile(input, file, object, key) {
var size = Math.round(file.size / 1024 / 1024),
sizeLimit = <%= Rails.application.config.default_image_max_size %>,
sizeLimitMo = 0,
controller;
if (input.hasAttribute('data-size-limit')) {
sizeLimit = input.getAttribute('data-size-limit');
}
sizeLimitMo = Math.round(sizeLimit / 1024 / 1024);
controller = new Vue.DirectUploadController(input, file, this.directUpload.url);
if (file.size > sizeLimit) {
alert("<%= t('admin.communication.blocks.alerts.file_is_too_big').html_safe %> (" + size + " Mo > " + sizeLimitMo + " Mo)");
return;
}
controller.start(function (blob) {
object[key] = {
id: blob.id,
signed_id: blob.signed_id,
filename: blob.filename
};
});
},
getFileUrl(signed_id, filename) {
return this.directUpload.blobUrlTemplate
.replace(':signed_id', signed_id)
.replace(':filename', filename);
},
getImageUrl(data) {
return this.getFileUrl(data.signed_id, "image_1024x.jpg");
},
handleSummernotes() {
var $summernoteElements = $('.summernote-vue:not(.is-initialized)');
$summernoteElements.each(function(index){
$summernoteElements.get(index).classList.add('is-initialized');
this.initSummernote($summernoteElements.get(index));
}.bind(this));
},
handleCodemirrors() {
var $codemirrorElements = $('.codemirror-vue');
$codemirrorElements.each(function(index){
this.initCodemirror($codemirrorElements.get(index));
}.bind(this));
},
initSummernote(element) {
var config = element.getAttribute('data-summernote-config') || 'default',
onChangeCallback = function(content) {
element.value = content;
element.dispatchEvent(new Event('input'))
},
onFocusCallback = function() {
$(this).parent().children('.note-editor').removeClass('note-editor--defocusing');
$(this).parent().children('.note-editor').addClass('note-editor--focus');
},
onBlurCallback = function() {
var editor = $(this).parent().children('.note-editor');
editor.addClass('note-editor--defocusing');
setTimeout(function () {
var defocusing = editor.hasClass('note-editor--defocusing'),
codeview = editor.hasClass('codeview');
if (defocusing && !codeview) {
editor.removeClass('note-editor--focus');
}
}, 1000);
},
onCursorCallback = function(event) {
// TODO vérifier si on est dans une note correctement, là ça ne marche pas si on se balade au clavier
var node = event.target.nodeName,
$editor = $(this).parent().children('.note-editor'),
$buttonNote = $editor.find('.note-btn-note');
if (node === 'NOTE') {
$buttonNote.addClass('active');
} else {
$buttonNote.removeClass('active');
}
};
$(element).summernote({
lang: '<%= current_interface_language.summernote_locale unless current_interface_language.summernote_locale.blank? %>',
toolbar: window.SUMMERNOTE_CONFIGS[config].toolbar, // Dependent of app/assets/javascripts/admin/plugins/summernote.js
followingToolbar: true,
disableDragAndDrop: true,
codemirror: window.codemirrorManager.defaultConfig(),
buttons: {
note: window.summernoteManager.noteButton
},
callbacks: {
onChange: onChangeCallback,
onChangeCodeview: onChangeCallback,
onBlur: onBlurCallback,
onFocus: onFocusCallback,
onMousedown: onCursorCallback,
onKeydown: onCursorCallback
}
});
},
initCodemirror(element) {
var config = window.codemirrorManager.defaultConfig(),
mode = element.getAttribute('data-codemirror-mode'),
editor;
config['mode'] = mode;
editor = CodeMirror.fromTextArea(element, config);
editor.on("change", function (instance, changeObj) {
element.value = instance.getValue();
element.dispatchEvent(new Event('input'));
});
}
},
updated: function() {
this.handleSummernotes();
},
mounted: function() {
this.handleSummernotes();
// Use Timeout to prevent display issues
setTimeout(this.handleCodemirrors.bind(this), 0);
// LanguageTool
document.initLanguageTool();
}
});
window.addEventListener('direct-upload:initialize', function (event) {
event.target.insertAdjacentHTML('afterend', `<progress value="0" max="100" style="width: 100%;"></progress>`)
});
window.addEventListener('direct-upload:progress', function (event) {
var progressBar = event.target.parentNode.querySelector('progress');
if (progressBar) {
progressBar.value = event.detail.progress;
}
});
window.addEventListener('direct-upload:end', function (event) {
var progressBar = event.target.parentNode.querySelector('progress');
if (progressBar) {
progressBar.parentNode.removeChild(progressBar);
}
});
window.addEventListener('load', function(){
setTimeout(function() {
app.mount('#app');
$("#block-form").on("ajax:success", function(e) {
var blockIdentifier = "<%= @block.id %>",
blockPath = "<%= admin_communication_block_path(@block) %>";
parent.window.osuny.contentEditor.offcanvas.onBlockSave(blockIdentifier, blockPath);
});
}, 1000);
});
</script>