Last active
August 28, 2020 21:27
-
-
Save jacobjones/cfd46093937826f8f1d36d8f3052ef87 to your computer and use it in GitHub Desktop.
EasyMDE for Episerver CMS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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 */ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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"; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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