Source: src/js/views/portals/editor/PortEditorMdSectionView.js

define([
  "underscore",
  "jquery",
  "models/portals/PortalImage",
  "views/MarkdownEditorView",
  "views/portals/editor/PortEditorSectionView",
  "views/portals/editor/PortEditorImageView",
  "text!templates/portals/editor/portEditorMdSection.html",
], (
  _,
  $,
  PortalImage,
  MarkdownEditor,
  PortEditorSectionView,
  ImageEdit,
  Template,
) => {
  /**
   * @class PortEditorMdSectionView
   * @classdesc A section of the Portal Editor for adding/editing a Markdown
   * page to a Portal
   * @classcategory Views/Portals/Editor
   * @augments PortEditorSectionView
   */
  const PortEditorMdSectionView = PortEditorSectionView.extend(
    /** @lends PortEditorMdSectionView.prototype */ {
      /**
       * The type of View this is
       * @type {string}
       * @readonly
       */
      type: "PortEditorMdSection",

      /**
       * The HTML classes to use for this view's element
       * @type {string}
       */
      className: `${PortEditorSectionView.prototype.className} port-editor-md`,

      /**
       * The HTML attributes to set on this view's element
       * @type {object}
       */
      attributes: {
        "data-category": "sections",
      },

      /**
       * The PortalSectionModel that is being edited
       * @type {PortalSectionModel}
       */
      model: undefined,

      /**
       * References to templates for this view. HTML files are converted to
       * Underscore.js templates
       * @type {Underscore.Template}
       */
      template: _.template(Template),

      /**
       * A jQuery selector for the element that will contain the ImageUploader
       * view
       * @type {string}
       */
      imageUploaderContainer: ".portal-display-image",

      /**
       * A jQuery selector for the element that will contain the markdown
       * section title text
       * @type {string}
       */
      titleEl: ".title",

      /**
       * A jQuery selector for the element that will contain the markdown
       * section introduction text
       * @type {string}
       */
      introEl: ".introduction",

      /**
       * A jQuery selector for the element that will contain the markdown editor
       * @type {string}
       */
      markdownEditorContainer: ".markdown-editor-container",

      /**
       * A reference to the PortalEditorView
       * @type {PortalEditorView}
       */
      editorView: undefined,

      /**
       * The type of section view this is
       * @type {string}
       * @readonly
       */
      sectionType: "freeform",

      /**
       * The events this view will listen to and the associated function to
       * call.
       * @type {object}
       */
      events: {},

      /**
       * Is executed when a new PortEditorMdSectionView is created
       * @param {object} options - A literal object with options to pass to the
       * view
       */
      initialize(options) {
        // Call the superclass initialize() function Passing the parameters to
        // the super class constructor
        PortEditorSectionView.prototype.initialize(options);
      },

      /**
       * Renders this view.
       * @returns {PortEditorMdSectionView} This view
       */
      render() {
        try {
          // Attach this view to the view Element
          this.$el.data("view", this);

          /**
           * PortalVizSection models aren't editable yet, so show a message and
           * exit.
           * @todo Create a PortalVizSectionView for PortalVizSection models,
           * rather than checking the section type here.
           */
          if (this.model.type === "PortalVizSection") {
            MetacatUI.appView.showAlert(
              "You're all set! A Fluid Earth Viewer data visualization will appear here.",
              "alert-info",
              this.$el,
            );
            this.$el.addClass("port-editor-viz");

            return this;
          }

          // Insert the template into the view
          this.$el
            .html(
              this.template({
                title: this.model.get("title"),
                titlePlaceholder: "Add a page title",
                introduction: this.model.get("introduction"),
                introPlaceholder:
                  "Add a sub-title or an introductory blurb about the content on this page.",
                // unique ID to use for the bootstrap accordion component, which
                // breaks when targeting two + components with the same ID
                cid: this.model.cid,
              }),
            )
            .data("view", this);

          // Render the Markdown Editor View
          const mdEditor = new MarkdownEditor({
            model: this.model.get("content"),
            markdownPlaceholder:
              "# Content\n\nAdd content here. Styling with markdown is supported.",
            previewPlaceholder:
              "Add some text in the Edit tab to show a preview here",
            showTOC: true,
          });
          mdEditor.render();
          this.$(this.markdownEditorContainer).html(mdEditor.el);

          // Attach the appropriate models to the textarea elements, so that
          // PortalEditorView.updateBasicText(e) can access them Don't use the
          // updateBasicText function on content/markdown sections, because we
          // don't want to "cleanXMLText" for markdown
          this.$(this.titleEl).data({ model: this.model });
          this.$(this.introEl).data({ model: this.model });

          // Add an ImageEdit view for the sectionImage If the section has no
          // image yet, add the default PortalImage model
          if (!this.model.get("image")) {
            this.model.set("image", new PortalImage({ nodeName: "image" }));
          }

          // Add the edit image view (incl. uploader) for the section image
          this.sectionImageUploader = new ImageEdit({
            model: this.model.get("image"),
            editorView: this.editorView,
            imageUploadInstructions: [
              "Drag & drop a high quality image here or click to upload",
              "Suggested image size: 1200 x 1000 pixels",
            ],
            nameLabel: false,
            urlLabel: false,
            imageTagName: "div",
            removeButton: false,
            imageWidth: false, // set to 100% in metacatui-common.css
            imageHeight: 300,
            minWidth: 800,
            minHeight: 300,
            maxHeight: 4000,
            maxWidth: 9000,
          });
          this.$(this.imageUploaderContainer).append(
            this.sectionImageUploader.el,
          );
          this.sectionImageUploader.render();
          this.$(this.imageUploaderContainer).data(
            "view",
            this.sectionImageUploader,
          );

          // Set listeners to auto-resize the height of the intoduction and
          // title textareas on user-input and on window resize events. This way
          // the fields appear more closely to how they will look on the portal
          // view.
          const view = this;
          $(window).resize(() => {
            view.$("textarea.auto-resize").trigger("textareaResize");
          });
          this.$("textarea.auto-resize").off("input textareaResize");
          this.$("textarea.auto-resize").on("input textareaResize", (e) => {
            view.resizeTextarea($(e.target));
          });

          // Make sure the textareas are the right size with their pre-filled
          // content the first time the section is viewed, because scrollHeight
          // is 0px when the element is not displayed.
          this.listenToOnce(this, "active", () => {
            view.resizeTextarea(view.$("textarea.auto-resize"));
          });

          this.listenTo(
            this.model.get("content"),
            "change",
            function showContentControls() {
              this.editorView.showControls();
            },
          );
          this.listenTo(
            this.model.get("image"),
            "change",
            function showImageControls() {
              this.editorView.showControls();
            },
          );

          return this;
        } catch (e) {
          // eslint-disable-next-line no-console
          console.log(
            "The portal editor markdown section view could not be rendered, error message: ",
            e,
          );
          return this;
        }
      },

      /**
       * resizeTextarea - Set the height of a textarea element based on its
       * scrollHeight.
       * @param  {jQuery} textareas The textarea element or elements to be
       * resized.
       */
      resizeTextarea(textareas) {
        try {
          if (textareas) {
            _.each(textareas, (textarea) => {
              const { style } = textarea;
              if (style) {
                style.height = "0px"; // note: textfield MUST have a min-height set
                style.height = `${textarea.scrollHeight}px`;
              }
            });
          }
        } catch (e) {
          // eslint-disable-next-line no-console
          console.log(`failed to resize textarea element. Error message: ${e}`);
        }
      },

      /**
       * showValidation - Display validation errors if any are retuned by the
       * PortalSection model
       */
      showValidation() {
        try {
          const errors = this.model.validate();

          _.each(
            errors,
            function showValidationError(errorMsg, category) {
              const categoryEls = this.$(`[data-category='${category}']`);

              // Use the showValidationMessage function from the parent view
              if (this.editorView && this.editorView.showValidationMessage) {
                this.editorView.showValidationMessage(categoryEls, errorMsg);
              }
            },
            this,
          );
        } catch (e) {
          // eslint-disable-next-line no-console
          console.error(e);
        }
      },
    },
  );

  return PortEditorMdSectionView;
});