Skip to content

Instantly share code, notes, and snippets.

@jacobjones
Last active August 28, 2020 21:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacobjones/cfd46093937826f8f1d36d8f3052ef87 to your computer and use it in GitHub Desktop.
Save jacobjones/cfd46093937826f8f1d36d8f3052ef87 to your computer and use it in GitHub Desktop.
EasyMDE for Episerver CMS
.markdown-editor {
width: 580px;
font-family: Verdana, Arial;
}
.markdown-editor .editor-toolbar {
border-top-left-radius: 0;
border-top-right-radius: 0;
border: 2px solid #b2b9c1;
border-bottom: 0;
}
.markdown-editor .CodeMirror {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border: 2px solid #b2b9c1;
border-top: 1px solid #b2b9c1;
}
.markdown-editor .CodeMirror, .markdown-editor .CodeMirror-scroll {
min-height: 200px; /* To make editor height static (i.e. prevent auto-growth), set 'height' to the desired height */
}
define([
"dojo/_base/declare",
"dojo/_base/config",
"dojo/_base/lang",
"dojo/ready",
"dojo/aspect",
"dojo/dom-class",
"dojo/on",
"dojo/when",
"dijit/_Widget",
"dijit/_TemplatedMixin",
"epi/epi",
"epi/dependency",
"epi/shell/widget/dialog/Dialog",
"epi/shell/widget/_ValueRequiredMixin",
"epi-cms/widget/ContentSelectorDialog",
"epi-cms/widget/LinkEditor",
"epi/i18n!epi/cms/nls/episerver.cms.widget.contentselector",
"epi/i18n!epi/cms/nls/episerver.cms.widget.editlink",
"/ClientResources/Editors/Markdown/EasyMDE/easymde.min.js",
"xstyle/css!./EasyMDE/easymde.min.css",
"xstyle/css!./MarkDownEditor.css"
],
function (
declare,
config,
lang,
ready,
aspect,
domClass,
on,
when,
_Widget,
_TemplatedMixin,
epi,
dependency,
Dialog,
_ValueRequiredMixin,
ContentSelectorDialog,
LinkEditor,
contentselectorResource,
editlinkResource,
EasyMDE
) {
return declare([_Widget, _TemplatedMixin, _ValueRequiredMixin],
{
editor: null, // The EasyMDE editor object
templateString: "<div class='markdown-editor dijitInline'><textarea id='editor-${id}'></textarea></div>",
onChange: function (value) {
/* Summary:
Notifies Episerver that the property value has changed. */
this.inherited(arguments);
},
constructor: function () {
/* Summary:
When the DOM has finished loading, we convert our textarea element to a EasyMDE editor.
We also wire up the 'blur' event to ensure editor changes propagate to the widget, i.e. property, value. */
this.inherited(arguments);
if (config.isDebug) {
console.log("Setting up EasyMDE markdown editor...");
}
aspect.after(this, "set", function (name, value) {
if (name === "value" && value) {
this._refreshEditor();
}
}, true);
ready(lang.hitch(this, function () {
this.editor = new EasyMDE({
element: document.getElementById("editor-" + this.id),
initialValue: this.get("value"),
placeholder: this.tooltip,
spellChecker: false,
insertTexts: {
epiImage: ["![#text#](#url#)", ""]
},
status: ["lines", "words"],
toolbar: !this.readOnly ? [
"bold", "italic", "heading", "heading-1", "heading-2", "heading-3", "|",
{
name: "epi-link",
className: "fa fa-link",
title: "Insert Episerver Link",
action: (editor) => { this._drawEpiLink(editor); }
},
{
name: "epi-image",
className: "fa fa-image",
title: "Insert Episerver Image",
action: (editor) => { this._drawEpiImage(editor); }
},
"|", "unordered-list", "ordered-list", "|", "preview"] : false
});
this.editor.codemirror.on("blur", lang.hitch(this, function () {
if (!epi.areEqual(this.get("value"), this.editor.value())) {
this.set("value", this.editor.value());
this.onChange();
}
}));
this._refreshEditor();
}));
},
_drawEpiLink: function (editor) {
// summary:
// Displays the Link Editor Dialog and generates a
// markdown link, based on epi-addon-tinymce/plugins/epi-link/epi-link.
// tags:
// private
var linkEditor = new LinkEditor({
baseClass: "epi-link-item",
//TODO: hardcoded for now
modelType: "EPiServer.Cms.Shell.UI.ObjectEditing.InternalMetadata.LinkModel",
// Hide text and target field from UI
hiddenFields: ["text", "target"]
});
var linkObject = {};
var cm = editor.codemirror;
var selectedText = cm.getSelection();
if (selectedText) {
linkObject.title = selectedText;
}
var dialogTitle = lang.replace(editlinkResource.title.template.create, editlinkResource.title.action);
var dialog = new Dialog({
title: dialogTitle,
dialogClass: "epi-dialog-portrait",
content: linkEditor,
defaultActionsVisible: false
});
dialog.startup();
linkEditor.set("value", linkObject);
dialog.show();
var options = editor.options;
var that = this;
dialog.on("execute", function () {
var value = linkEditor.get("value");
var linkObject = lang.clone(value);
//Destroy the editor when the dialog closes
linkEditor.destroy();
linkEditor = null;
that._replaceSelection(cm, options.insertTexts.link, linkObject.title, linkObject.href);
});
},
_drawEpiImage: function (editor) {
// summary:
// Displays the Image Content Selector Dialog and generates a
// markdown link, based on epi-addon-tinymce/FileBrowser.
// tags:
// private
var registry = dependency.resolve("epi.storeregistry");
var store = registry.get("epi.cms.contentdata");
var contentRepositoryDescriptors = dependency.resolve("epi.cms.contentRepositoryDescriptors");
var settings = contentRepositoryDescriptors.get("media");
var contentSelector = new ContentSelectorDialog({
canSelectOwnerContent: false,
showButtons: false,
roots: settings.roots,
multiRootsMode: true,
showRoot: true,
allowedTypes: ["episerver.core.icontentimage"]
});
var dialog = new Dialog({
title: contentselectorResource.title,
dialogClass: "epi-dialog-portrait",
content: contentSelector
});
dialog.own(contentSelector);
dialog.show();
var cm = editor.codemirror;
var options = editor.options;
var that = this;
on.once(dialog, "execute", function () {
var contentLink = contentSelector.get("value");
if (!contentLink) {
return;
}
when(store.get(contentLink)).then(function (content) {
that._replaceSelection(cm, options.insertTexts.epiImage, content.properties["altText"] || content.name, content.permanentLink);
});
});
},
_replaceSelection: function (cm, startEnd, text, url) {
// summary:
// Replaces the selection based on the given text and URL.
// Based on the private EasyMDE method of the same name.
// tags:
// private
if (/ editor - preview - active /.test(cm.getWrapperElement().lastChild.className))
return;
var start = startEnd[0];
var end = startEnd[1];
var startPoint = {},
endPoint = {};
Object.assign(startPoint, cm.getCursor("start"));
Object.assign(endPoint, cm.getCursor("end"));
// Indicates that the selected text will be updated/changed
var update = text && !start.includes("#text#") && !end.includes("#text#");
if (text) {
start = start.replace("#text#", text);
end = end.replace("#text#", text);
}
if (url) {
start = start.replace("#url#", url); // url is in start for upload-image
end = end.replace("#url#", url);
}
var selectedText = cm.getSelection();
cm.replaceSelection(start + (update ? text : selectedText) + end);
startPoint.ch += start.length;
if (startPoint !== endPoint) {
endPoint.ch += start.length + (update ? text.length - selectedText.length : 0);
}
cm.setSelection(startPoint, endPoint);
cm.focus();
},
resize: function () {
/* Summary:
The resize() function is called when the tab strip containing this widget switches tabs.
When this happens we need to refresh the editor to ensure it displays property.
This is a well-known characteristic of CodeMirror, which is part of the EasyMDE editor. */
this.inherited(arguments);
this._refreshEditor();
},
_refreshEditor: function () {
/* Summary:
This function refreshes the editor, and ensures its value matches the current property value.
It also switches to preview mode, making the editor read-only, if the underlying property
is in read-only mode. */
if (!this.editor) {
return;
}
if (typeof this.get("value") !== "object" && !epi.areEqual(this.editor.value(), this.get("value"))) {
this.editor.value(this.get("value"));
}
if (this.readOnly) {
var previewElement = this.editor.codemirror.getWrapperElement().lastChild;
var previewActive = domClass.contains(previewElement, "editor-preview-active");
if (!previewActive) {
this.editor.togglePreview();
} else {
previewElement.innerHTML = this.editor.options.previewRender(this.editor.value(), previewElement);
}
}
this.editor.codemirror.refresh();
}
});
});
[EditorDescriptorRegistration(
TargetType = typeof(string),
UIHint = UIHint,
EditorDescriptorBehavior = EditorDescriptorBehavior.PlaceLast)]
public class MarkdownEditorDescriptor : EditorDescriptor
{
public const string UIHint = "Markdown";
public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
{
base.ModifyMetadata(metadata, attributes);
metadata.ClientEditingClass = "editors/Markdown/MarkdownEditor";
}
}
<?xml version="1.0" encoding="utf-8"?>
<module>
<dojo>
<paths>
<add name="editors" path="Editors" />
</paths>
</dojo>
</module>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment