noesya/osuny

View on GitHub
app/views/admin/communication/blocks/edit.html.erb

Summary

Maintainability
Test Coverage
<% 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">&nbsp;</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>