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;
});