Created
October 22, 2015 09:20
-
-
Save gregwiechec/fed65b5d9d77395340cd to your computer and use it in GitHub Desktop.
EPiServer compare with markup
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
ins { | |
background-color: #cfc; | |
text-decoration: none; | |
} | |
del { | |
color: #999; | |
background-color:#FEC8C8; | |
} | |
.epi-form-container__section__row__editor--extendedCompare { | |
display: inline-block; | |
margin: 0 0 10px 0; | |
} |
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
using EPiServer.Core; | |
using EPiServer.ServiceLocation; | |
using EPiServer.Shell; | |
namespace ExtendingCompareVersion.Business.ViewConfiguration | |
{ | |
[ServiceConfiguration(typeof(EPiServer.Shell.ViewConfiguration))] | |
public class CompareWithMarkup : ViewConfiguration<IContentData> | |
{ | |
public CompareWithMarkup() | |
{ | |
this.Key = "compareWithMarkup"; | |
this.ControllerType = "epi-cms/compare/views/CompareView"; | |
this.ViewType = "alloy/editors/compareWithMarkupView"; | |
this.IconClass = "epi-iconCompare"; | |
this.HideFromViewMenu = true; | |
} | |
} | |
} |
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/lang", | |
"dojo/topic", | |
"epi/shell/widget/Iframe", | |
"epi-cms/compare/views/SideBySideCompareView", | |
"epi/shell/_ContextMixin" | |
], | |
function ( | |
declare, | |
lang, | |
topic, | |
Iframe, | |
SideBySideCompareView, | |
_ContextMixin | |
) { | |
return declare([SideBySideCompareView, _ContextMixin], { | |
//?first=48012_1425533&second=48012_1424347&forceLanguage=no | |
_setRightVersionUrlAttr: function (url) { | |
this._rightIframe.load("/Plugins/VisualPageCompare.aspx?first=" + this.model.leftVersion.contentLink + "&second=" + this.model.rightVersion.contentLink); | |
}, | |
_setupComparePanes: function() { | |
this.inherited(arguments); | |
var orientation = this.model.orientation; | |
this._rightIframe.set('style', orientation === "vertical" ? "width: 100%;" : "height: 100%"); | |
this.mainLayoutContainer.layout(); | |
}, | |
contextChanged: function (ctx, callerData) { | |
this.set('RightVersionUrl', ''); | |
} | |
}); | |
}); |
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("epi-cms/compare/command/CompareCommandProvider", [ | |
"dojo/_base/declare", | |
"dojo/when", | |
"epi-cms/component/command/_GlobalToolbarCommandProvider", | |
"epi/dependency", | |
"./CompareSettingsModel", | |
"./CompareViewSelection", | |
"epi/shell/command/ToggleCommand", | |
"epi-cms/command/_NonEditViewCommandMixin", | |
"epi/i18n!epi/cms/nls/episerver.cms.compare.command" | |
], function ( | |
declare, | |
when, | |
_GlobalToolbarCommandProvider, | |
dependency, | |
CompareSettingsModel, | |
CompareViewSelection, | |
ToggleCommand, | |
_NonEditViewCommandMixin, | |
resources | |
) { | |
var NonEditViewToggleCommand = declare([ToggleCommand, _NonEditViewCommandMixin]); | |
return declare([_GlobalToolbarCommandProvider], { | |
// summary: | |
// A command provider providing compare commands to the global toolbar | |
constructor: function() { | |
this.profile = this.profile || dependency.resolve("epi.shell.Profile"); | |
}, | |
postscript: function () { | |
this.inherited(arguments); | |
var model = new CompareSettingsModel(); | |
/* ********************************************************************************************************************************** */ | |
/* Code in this section is different than original file */ | |
/* ********************************************************************************************************************************** */ | |
when(this.profile.get("epi.selected-comparemode-id"), function (mode) { | |
model.set("mode", "sidebysidecompare"); | |
}); | |
model.modeOptions.push( | |
{ label: "Extended All properties Compare", value: "extendedAllPropertiesCompare", iconClass: "epi-iconForms" }); | |
model.modeOptions.push( | |
{ label: "Compare with markup", value: "compareWithMarkup", iconClass: "epi-iconForms" }); | |
/* ********************************************************************************************************************************** */ | |
// Need to have a settings object as well, since the global menu builder looks a this for category | |
var settings = { category: "compare" }; | |
this.add("commands", new NonEditViewToggleCommand({ | |
category: "compare", | |
settings: settings, | |
label: resources.togglecompare.label, | |
iconClass: "epi-iconCompare", | |
model: model, | |
property: "enabled" | |
})); | |
this.add("commands", new CompareViewSelection({ | |
category: "compare", | |
settings: settings, | |
model: model, | |
label: resources.compareviewselection.label, | |
optionsLabel: resources.compareviewselection.label, | |
optionsProperty: "modeOptions", | |
property: "mode" | |
})); | |
} | |
}); | |
}); |
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("epi/shell/widget/WidgetSwitcher", [ | |
"dojo/_base/array", | |
"dojo/_base/declare", | |
"dojo/_base/kernel", | |
"dojo/_base/lang", | |
"dojo/Deferred", | |
"dojo/topic", | |
"dojo/when", | |
"epi/shell/_ContextMixin", | |
"epi/shell/layout/AnimatedCardContainer", | |
"epi/shell/TypeDescriptorManager" | |
], function ( | |
array, | |
declare, | |
kernel, | |
lang, | |
Deferred, | |
topic, | |
when, | |
_ContextMixin, | |
AnimatedCardContainer, | |
TypeDescriptorManager | |
) { | |
return declare([AnimatedCardContainer, _ContextMixin], { | |
// summary: | |
// A container that handles showing a child widget based on either a | |
// specific view request or a change in the current context. | |
// tags: | |
// internal | |
// componentConstructorArgs: Object | |
// Arguments that must be passed to constructor when new component is created. | |
componentConstructorArgs: null, | |
// supportedContextTypes: Array | |
// String array of supported context data types. | |
// This option should be defined if we need to react only on context changes with specific data type. | |
// Context changes are ignored if set to empty array. | |
supportedContextTypes: null, | |
constructor: function () { | |
this._viewHistory = []; | |
}, | |
postMixInProperties: function () { | |
// summary: | |
// Verifies and fixes the list of supported context data types. | |
// tags: | |
// protected | |
this.inherited(arguments); | |
if (this.supportedContextTypes) { | |
if (!lang.isArray(this.supportedContextTypes)) { | |
this.supportedContextTypes = [this.supportedContextTypes]; | |
} | |
if (!array.every(this.supportedContextTypes, function (type) { return typeof type == "string"; })) { | |
throw new Error("supportedContextTypes must be string, array of strings or null."); | |
} | |
} | |
}, | |
postCreate: function () { | |
// summary: | |
// If the current context is available it loads the initial view component for it. Also subscribes | |
// to view component change requests. | |
// tags: | |
// protected | |
this.inherited(arguments); | |
when(this.getCurrentContext(), lang.hitch(this, this.contextChanged)); | |
this.own( | |
topic.subscribe("/epi/shell/action/changeview", lang.hitch(this, "viewComponentChangeRequested")), | |
topic.subscribe("/epi/shell/action/changeview/back", lang.hitch(this, "_viewComponentBackRequested")), | |
topic.subscribe("/epi/shell/action/changeview/deactivate", lang.hitch(this, "_viewComponentDeactivate")), | |
topic.subscribe("/epi/shell/action/changeview/updatestate", lang.hitch(this, "_changeViewState")) | |
); | |
}, | |
contextChanged: function (/*Object*/context, data) { | |
// summary: | |
// Called when the current context changes. | |
// tags: | |
// protected | |
if (!this._isContextTypeSupported(context)) { | |
return; | |
} | |
var view = context.customViewType || (!!data ? data.viewType : null); | |
var currentWidgetInfo = this._getCurrentWidgetInfo(); | |
//NOTE: when we change so that we can route to different views this should be rewritten. | |
if (data && data.contextIdSyncChange && currentWidgetInfo) { | |
view = currentWidgetInfo.type; | |
data = lang.mixin({}, data, { | |
viewName: currentWidgetInfo.viewName, | |
availableViews: currentWidgetInfo.availableViews | |
}); | |
} | |
if (!view && context.dataType) { | |
// If there is no custom view type, fall back to the widget for the type. | |
// For backwards compatibility, check the obsoleted property mainWidgetType | |
view = TypeDescriptorManager.getValue(context.dataType, "mainWidgetType"); | |
} | |
if (view) { | |
this._getObject(view, null, context, data); | |
} else { | |
var suggestedView = TypeDescriptorManager.getValue(context.dataType, "defaultView"), | |
availableViews = this._getAvailableViews(context.dataType), | |
requestedViewName = data ? data.viewName : null; | |
var viewsArr = [ | |
"view", | |
"sidebysidecompare", | |
"allpropertiescompare" | |
]; | |
/* ********************************************************************************************************************************** */ | |
/* Code in this section is different than original file */ | |
/* ********************************************************************************************************************************** */ | |
viewsArr.push("compareWithMarkup"); | |
viewsArr.push("extendedAllPropertiesCompare"); | |
/* ********************************************************************************************************************************** */ | |
// TODO: consider removing reference to "view"(preview) when refactoring the view rerouting | |
if (currentWidgetInfo && viewsArr.indexOf(currentWidgetInfo.viewName) >= 0) { | |
// if we're viewing the preview we should keep that view as long as it's available. | |
array.some(availableViews, function (availableView) { | |
if (availableView.key == currentWidgetInfo.viewName) { | |
suggestedView = currentWidgetInfo.viewName; | |
return true; | |
} | |
return false; | |
}); | |
} | |
var viewToLoad = this._getViewByKey(requestedViewName || suggestedView, availableViews); | |
if (!viewToLoad) { | |
throw "No default view found for " + context.dataType; | |
} | |
this._loadViewComponentByConfiguration(viewToLoad, availableViews, null, context, data); | |
} | |
}, | |
_getViewByKey: function (key, availableViews) { | |
return array.filter(availableViews, function (availableView) { | |
return availableView.key === key; | |
})[0]; | |
}, | |
_getAvailableViews: function (dataType) { | |
var availableViews = TypeDescriptorManager.getAndConcatenateValues(dataType, "availableViews"), | |
disabledViews = TypeDescriptorManager.getValue(dataType, "disabledViews"); | |
var filteredViews = array.filter(availableViews, function (availableView) { | |
return array.every(disabledViews, function (disabledView) { | |
return availableView.key !== disabledView; | |
}); | |
}); | |
return filteredViews.sort(function (x, y) { | |
return x.sortOrder < y.sortOrder ? -1 : 1; | |
}); | |
}, | |
_loadViewComponentByConfiguration: function (view, availableViews, args, context, data) { | |
data = lang.mixin({}, data, { | |
viewName: view.key, | |
availableViews: availableViews | |
}); | |
this._getObject(view.controllerType, args, context, data); | |
}, | |
viewComponentChangeRequested: function (/*String*/type, /*Object*/args, /*Object*/data) { | |
// summary: | |
// Called when a new view component is requested. | |
// type: String | |
// Specify component widget type or a key which refers to a view configuration. | |
// args: Object | |
// Arguments used to instantiate component widget. | |
// data: Object | |
// data passed to the view when calling updateView. | |
// tags: | |
// protected | |
when(this.getCurrentContext(), lang.hitch(this, function (currentContext) { | |
var dataType = currentContext ? currentContext.dataType : null; | |
// If no view or widget type is supplied, then use the default view for the content type | |
if (!type && dataType) { | |
type = TypeDescriptorManager.getValue(dataType, "defaultView"); | |
} | |
var availableViews = this._getAvailableViews(dataType), | |
view = this._getViewByKey(type, availableViews); | |
if (view) { | |
// A view configuration found, means that new view is being requested by key | |
this._loadViewComponentByConfiguration(view, availableViews, args, currentContext, data); | |
} else { | |
// Otherwise, by component type. (Backward compatibility) | |
this._getObject(type, args, currentContext, data); | |
} | |
})); | |
}, | |
_viewComponentBackRequested: function (forceReload) { | |
var previousWidgetInfo = this._getPreviousWidgetInfo(); | |
if (previousWidgetInfo) { | |
if (typeof forceReload === "undefined") { | |
// forceReload should be false by default, which means that we will skip updating view. | |
forceReload = false; | |
} | |
var data = { | |
skipUpdateView: !forceReload, | |
availableViews: previousWidgetInfo.availableViews, | |
viewName: previousWidgetInfo.viewName | |
}; | |
when(this.getCurrentContext(), lang.hitch(this, function (currentContext) { | |
this._getObject(previousWidgetInfo.type, null, currentContext, data); | |
})); | |
} | |
}, | |
_viewComponentDeactivate: function (view) { | |
kernel.deprecated("topic: /epi/shell/action/changeview/deactivate"); | |
this._viewHistory = this._viewHistory.filter(function (element) { | |
return element.viewName !== view; | |
}); | |
}, | |
_changeViewState: function (state) { | |
var currentWidgetInfo = this._getCurrentWidgetInfo(); | |
if (currentWidgetInfo) { | |
lang.mixin(currentWidgetInfo, state); | |
} | |
}, | |
_getPreviousWidgetInfo: function () { | |
var history = this._viewHistory; | |
return (history.length > 1) ? history[history.length - 2] : null; | |
}, | |
_getCurrentWidgetInfo: function () { | |
var history = this._viewHistory; | |
return (history.length > 0) ? history[history.length - 1] : null; | |
}, | |
_isContextTypeSupported: function (context) { | |
// summary: | |
// Verifies if data type of specified context is supported by this instance | |
// tags: | |
// private | |
if (!context) { | |
return false; | |
} | |
if (!this.supportedContextTypes) { | |
return true; | |
} | |
return array.some(this.supportedContextTypes, function (type) { return context.type === type; }); | |
}, | |
_getObject: function (type, args, context, data) { | |
// summary: | |
// Gets an instance for the given type if it is different from | |
// the current view component type. | |
// tags: | |
// private | |
if (!type) { | |
return; | |
} | |
var current = this.selectedChildWidget; | |
var func = lang.hitch(this, function () { | |
when(this._testComponentInstanceOf(current, type), lang.hitch(this, function (result) { | |
if (result) { | |
this._updateHistory(type, data, context); | |
this._updateView(current, context, data); | |
this._onViewChanged(type, args, data); | |
} | |
else { | |
this._changeViewComponent(type, args, context, data); | |
} | |
})); | |
}); | |
if (current && current.savePendingChanges) { | |
when(current.savePendingChanges(), func); | |
} else { | |
func(); | |
} | |
}, | |
_changeViewComponent: function (type, args, context, data) { | |
// summary: | |
// Remove the current view component and add the new one. | |
// tags: | |
// private | |
when(this._getChildByType(type), lang.hitch(this, function (component) { | |
var current = this.selectedChildWidget; | |
// suspend the current one before changing to the next | |
if (current) { | |
current.set("isActive", false); | |
} | |
if (component) { | |
// activate the component | |
component.set("isActive", true); | |
if (data && data.treatAsSecondaryView) { | |
this.selectSecondaryChild(component); | |
} else { | |
this.selectChild(component); | |
} | |
this._updateHistory(type, data, context); | |
this._updateView(component, context, data, true); | |
this._onViewChanged(type, args, data); | |
} | |
else { | |
require([type], lang.hitch(this, function (componentClass) { | |
var mixedArgs = lang.mixin(lang.mixin({}, this.componentConstructorArgs), args); | |
component = new componentClass(mixedArgs); | |
//component.transitionMode = "reveal"; | |
component.fitContainer = true; | |
this.addChild(component); | |
var select; | |
if (data && data.treatAsSecondaryView) { | |
select = this.selectSecondaryChild(component); | |
} else { | |
select = this.selectChild(component); | |
} | |
when(select, lang.hitch(this, function () { | |
this._updateHistory(type, data, context); | |
this._updateView(component, context, data, true); | |
this._onViewChanged(type, args, data); | |
})); | |
})); | |
} | |
})); | |
}, | |
_updateView: function (component, context, data, widgetResumed) { | |
if (component && !component._started && typeof component.startup === "function") { | |
// Ensure component ready. | |
component.startup(); | |
} | |
if (component && typeof component.updateView === "function") { | |
component.updateView(data, context, { widgetResumed: widgetResumed }); | |
} | |
}, | |
_getChildByType: function (type) { | |
var def = new Deferred(); | |
require([type], lang.hitch(this, function (componentClass) { | |
var components = array.filter(this.getChildren(), function (child) { | |
return child.constructor === componentClass; | |
}, this); | |
def.resolve(components.length ? components[0] : null); | |
})); | |
return def; | |
}, | |
_testComponentInstanceOf: function (component, type) { | |
var def = new Deferred(); | |
if (component) { | |
if (typeof type === "string") { | |
require([type], function (componentClass) { | |
def.resolve(component && component.constructor === componentClass); | |
}); | |
} else { | |
def.resolve(component && component.constructor === type.constructor); | |
} | |
} else { | |
def.resolve(false); | |
} | |
return def; | |
}, | |
_onViewChanged: function (type, args, data) { | |
topic.publish("/epi/shell/action/viewchanged", type, args, data); | |
}, | |
_updateHistory: function (/*string*/type, data, context) { | |
// summary: | |
// Add data to widget history store | |
// Keep total items in store always 2 | |
// tags: | |
// private | |
var history = this._viewHistory, | |
length = history.length, | |
item = this.createStoreItem(type, data, context); | |
if (length && history[length - 1].id === item.id) { | |
return; | |
} | |
if (length === 3) { | |
history.shift(); | |
} | |
history.push(item); | |
}, | |
createStoreItem: function (/*string*/type, data, context) { | |
// summary: | |
// Create new object that will be put to store | |
// tags: | |
// protected | |
var storeItem = { type: type, id: type }; | |
if (data) { | |
var availableViews = data.availableViews || (context && this._getAvailableViews(context.dataType)); | |
var viewName = data.viewName || this._getViewName(availableViews, context); | |
storeItem = lang.mixin(storeItem, { | |
availableViews: availableViews, | |
viewName: viewName | |
}); | |
storeItem.id += "#" + viewName; | |
} | |
return storeItem; | |
}, | |
_getViewName: function (availableViews, context) { | |
if (!context) { | |
return null; | |
} | |
var selectedView = this._getViewByKey(TypeDescriptorManager.getValue(context.dataType, "defaultView"), availableViews); | |
if (!selectedView) { | |
return null; | |
} | |
return selectedView.key; | |
} | |
}); | |
}); |
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
using EPiServer.Core; | |
using EPiServer.ServiceLocation; | |
using EPiServer.Shell; | |
namespace ExtendingCompareVersion.Business.ViewConfiguration | |
{ | |
[ServiceConfiguration(typeof(EPiServer.Shell.ViewConfiguration))] | |
public class ExtendedAllPropertiesCompare : ViewConfiguration<IContentData> | |
{ | |
public ExtendedAllPropertiesCompare() | |
{ | |
this.Key = "extendedAllPropertiesCompare"; | |
this.ControllerType = "epi-cms/compare/views/CompareView"; | |
this.ViewType = "alloy/editors/extendedAllPropertiesCompareView"; | |
this.IconClass = "epi-iconCompare"; | |
this.HideFromViewMenu = true; | |
} | |
} | |
} |
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/lang", | |
"dojo/Deferred", | |
"dojo/when", | |
"epi-cms/compare/views/AllPropertiesCompareView", | |
"epi/shell/widget/FormContainer", | |
"./extendedAllPropertiesTransformer", | |
"./extendedFormField" | |
], function ( | |
declare, | |
lang, | |
Deferred, | |
when, | |
AllPropertiesCompareView, | |
FormContainer, | |
ExtendedAllPropertiesTransformer, | |
ExtendedFormField | |
) { | |
return declare("alloy.editors.extendedAllPropertiesCompareView", [AllPropertiesCompareView], { | |
_createForm: function () { | |
// summary: | |
// Setup the edit form. | |
// tags: | |
// private | |
return new FormContainer(lang.mixin({ | |
readOnly: !this.viewModel.canChangeContent(), | |
metadata: this.viewModel.metadata, | |
metadataTransformer: new ExtendedAllPropertiesTransformer({ model: this.model }), | |
baseClass: "epi-cmsEditingForm epi-form-container epi-form-container--compare" | |
}, this.formSettings)); | |
} | |
}); | |
}); |
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/lang", | |
"epi-cms/compare/AllPropertiesTransformer" | |
], | |
function ( | |
declare, | |
lang, | |
AllPropertiesTransformer | |
) { | |
return declare([AllPropertiesTransformer], { | |
_createPropertyWidget: function() { | |
var editor = this.inherited(arguments); | |
editor[0].settings._hint = "extendedCompare"; | |
editor[1].settings._type = "extendedCompare"; | |
return editor; | |
} | |
}); | |
}); |
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 | |
"dojo/_base/declare", | |
"dojo/_base/array", | |
"dojo/_base/lang", | |
"dojo/when", | |
"dojo/dom-style", | |
"dojo/query", | |
"dijit/form/Button", | |
"dijit/Dialog", | |
"epi/dependency", | |
"epi-cms/compare/FormField", | |
"epi/shell/form/formFieldRegistry", | |
"xstyle/css!./compare.css" | |
], | |
function ( | |
// dojo | |
declare, | |
array, | |
lang, | |
when, | |
domStyle, | |
query, | |
Button, | |
Dialog, | |
dependency, | |
FormField, | |
registry | |
) { | |
var module = declare([FormField], { | |
buildRendering: function () { | |
this.inherited(arguments); | |
this.own(this._compareButton = new Button({ | |
"class": "epi-form-container__section__row__copy-button epi-chromeless", | |
iconClass: "epi-icon-compare--copy", | |
label: "Compare", | |
title: "Compare two properties" | |
}).placeAt(this.domNode, "last")); | |
domStyle.set(this._compareButton.domNode, { | |
"margin-right": '100px' | |
}); | |
this.own(this.resultDialog = new Dialog({ | |
title: 'Compare properties - ' + this.label, | |
content: '', | |
style: "width: 800px" | |
})); | |
this._compareButton.on("click", lang.hitch(this, function () { | |
var registry = dependency.resolve("epi.storeregistry"); | |
var store = registry.get("compare.htmlDiff"); | |
var left = this.getPropertyValue(this.model.rightMetadata.properties, this.name, '.epi-form-container__section__row__editor--extendedCompare'); | |
var right = this.getPropertyValue(this.model.leftMetadata.properties, this.name, '.epi-form-container__section__row__editor--field'); | |
when(store.add({ left: left, right: right, modelType: this.getModelType() }), lang.hitch(this, function (result) { | |
var html = result.data; | |
this.resultDialog.set('content', html); | |
this.resultDialog.show(); | |
})); | |
})); | |
}, | |
getModelType: function() { | |
var name = this.name.toLowerCase(); | |
var modelType = array.filter(this.model.leftMetadata.properties, function (item) { | |
return item.name.toLowerCase() == name; | |
})[0].modelType; | |
if (modelType == 'EPiServer.Core.ContentArea') { | |
return 2; | |
} | |
return 1; | |
}, | |
getPropertyValue: function (properties, name, widgetSelector) { | |
name = name.toLowerCase(); | |
var property = array.filter(properties, function (item) { | |
return item.name.toLowerCase() == name; | |
})[0]; | |
if (property.modelType == "EPiServer.Core.ContentArea") { | |
var result = []; | |
var caItems = query(widgetSelector + ' div.dijitTreeNode div.epi-tree-mngr--block', this.domNode); | |
for (var i = 0; i < caItems.length; i++) { | |
result.push(caItems[i].outerHTML); | |
} | |
return result; | |
} | |
var value = property.initialValue; | |
// for text area split use br to join lines | |
if (property.modelType == "System.String[]") { | |
value = encodeURIComponent(value.join('<br/>')); | |
} | |
return [value]; | |
} | |
}); | |
function fieldFactory(widget, parent) { | |
var wrapper = new module({ | |
labelTarget: widget.checkbox ? widget.checkbox.id : widget.id, | |
label: widget.label, | |
tooltip: widget.tooltip, | |
name: widget.name, | |
groupName: widget.groupName.toLowerCase(), | |
readonlyIconDisplay: widget.readOnly, | |
hasFullWidthValue: widget.useFullWidth | |
}); | |
wrapper.own(widget.watch("readOnly", function (name, oldValue, newValue) { | |
wrapper.set("readonlyIconDisplay", newValue); | |
})); | |
return wrapper; | |
} | |
registry.add([ | |
{ | |
type: registry.type.field, | |
hint: "extendedCompare", | |
factory: fieldFactory | |
}, | |
{ | |
type: "extendedCompare", | |
hint: "", | |
factory: registry.get('compare', '') | |
} | |
]); | |
return module; | |
}); |
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
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Web.Mvc; | |
using EPiServer.Legacy.UI.Edit.PageCompare.Core.Providers; | |
using EPiServer.Shell.Services.Rest; | |
using HtmlAgilityPack; | |
using NuGet; | |
namespace ExtendingCompareVersion.Business | |
{ | |
[RestStore("htmlDiffStore")] | |
public class HtmlDiffStore : RestControllerBase | |
{ | |
[HttpPost] | |
public ActionResult Post(CompareParameter compareParameter) | |
{ | |
string compareResult; | |
compareParameter.Left = ClearText(compareParameter.Left); | |
compareParameter.Right = ClearText(compareParameter.Right); | |
if (compareParameter.ModelType == CompareModelType.ContentArea) | |
{ | |
compareResult = new ContentAreaComparer().Compare(compareParameter); | |
} | |
else | |
{ | |
compareResult = CompareStrings(compareParameter); | |
} | |
return Rest(new HtmlCompareResult() | |
{ | |
Data = compareResult | |
}); | |
} | |
private string CompareStrings(CompareParameter compareParameter) | |
{ | |
var htmlDiff = new HtmlDiff(compareParameter.Left.First(), compareParameter.Right.First()); | |
return htmlDiff.Build(); | |
} | |
private IEnumerable<string> ClearText(IEnumerable<string> values) | |
{ | |
if (values == null) | |
{ | |
return Enumerable.Empty<string>(); | |
} | |
return values.Select(ClearText).ToList(); | |
} | |
private string ClearText(string value) | |
{ | |
if (string.IsNullOrEmpty(value)) | |
{ | |
return ""; | |
} | |
value = System.Web.HttpUtility.UrlDecode(value); | |
var htmlDocument = new HtmlDocument(); | |
htmlDocument.LoadHtml(value); | |
SortAttributes(htmlDocument.DocumentNode); | |
value = htmlDocument.DocumentNode.OuterHtml; | |
value = value.Replace("dijitTextBoxReadOnly ", ""); | |
value = value.Replace("dijitTextAreaReadOnly ", ""); | |
return value; | |
} | |
private static void SortAttributes(HtmlNode documentNode) | |
{ | |
var attributes = documentNode.Attributes.OrderBy(a => a.Name).ToList(); | |
documentNode.Attributes.RemoveAll(); | |
documentNode.Attributes.AddRange(attributes); | |
foreach (var childNode in documentNode.ChildNodes) | |
{ | |
if (childNode is HtmlTextNode) | |
{ | |
continue; | |
} | |
SortAttributes(childNode); | |
} | |
} | |
} | |
[Serializable] | |
public class CompareParameter | |
{ | |
public IEnumerable<string> Left { get; set; } | |
public IEnumerable<string> Right { get; set; } | |
public CompareModelType ModelType { get; set; } | |
} | |
public enum CompareModelType | |
{ | |
String = 1, | |
ContentArea = 2 | |
} | |
[Serializable] | |
public class HtmlCompareResult | |
{ | |
public string Data { get; set; } | |
} | |
public class ContentAreaComparer | |
{ | |
public string Compare(CompareParameter compareParameter) | |
{ | |
var leftItems = this.InitList(compareParameter.Left); | |
var rightItems = this.InitList(compareParameter.Right); | |
var listsComparer = new ListsComparer(); | |
var listItems = listsComparer.Compare(rightItems, leftItems).ToList(); | |
var result = new StringBuilder(); | |
for (int index = 0; index < listItems.Count; index++) | |
{ | |
var listItem = listItems[index]; | |
var html = ((ContentAreaElement) listItem.Value).Html; | |
var htmlDocument = new HtmlDocument(); | |
htmlDocument.LoadHtml(html); | |
var rootNode = htmlDocument.DocumentNode.SelectSingleNode("//div[contains(@class, 'dijitTreeNode')]"); | |
rootNode.Attributes["id"].Value = "compareResult" + index; | |
rootNode.Attributes["widgetId"].Remove(); | |
if (listItem.State == CompareState.Equal) | |
{ | |
result.AppendLine(htmlDocument.DocumentNode.OuterHtml); | |
continue; | |
} | |
var textNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='dijitTreeLabel']"); | |
if (listItem.State == CompareState.Deleted) | |
{ | |
textNode.InnerHtml = string.Format("<ins class='diffins'>{0}</ins>", textNode.InnerText); | |
} | |
else | |
{ | |
textNode.InnerHtml = string.Format("<del class='diffdel'>{0}</ins>", textNode.InnerText); | |
//$"<del class='diffdel'>{textNode.InnerText}</ins>"; | |
} | |
result.AppendLine(htmlDocument.DocumentNode.OuterHtml); | |
} | |
return result.ToString(); | |
} | |
private IEnumerable<IComparable> InitList(IEnumerable<string> list) | |
{ | |
var contentAreaElements = new List<ContentAreaElement>(); | |
foreach (var item in list) | |
{ | |
var htmlDocument = new HtmlDocument(); | |
htmlDocument.LoadHtml(item); | |
var textNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='dijitTreeLabel']"); | |
var contentAreaElement = new ContentAreaElement(item, textNode == null ? "" : textNode.InnerText); | |
contentAreaElements.Add(contentAreaElement); | |
} | |
return contentAreaElements; | |
} | |
public class ContentAreaElement : IComparable | |
{ | |
public ContentAreaElement(string html, string compareKey) | |
{ | |
this.Html = html; | |
this.CompareKey = compareKey; | |
} | |
public string Html { get; set; } | |
public string CompareKey { get; set; } | |
public int CompareTo(object obj) | |
{ | |
var otherKey = ((ContentAreaElement)obj).CompareKey; | |
return string.Compare(otherKey, this.CompareKey, StringComparison.Ordinal); | |
} | |
} | |
} | |
} |
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
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
namespace ExtendingCompareVersion.Business | |
{ | |
public class ListsComparer | |
{ | |
public IEnumerable<ListItem> Compare(IEnumerable<IComparable> source, IEnumerable<IComparable> target ) | |
{ | |
var sourceList = source.ToList(); | |
var targetList = target.ToList(); | |
var listItems = new List<ListItem>(); | |
var sourceIndex = 0; | |
var targetIndex = 0; | |
while (sourceIndex < sourceList.Count && targetIndex < targetList.Count) | |
{ | |
var sourceElement = sourceList[sourceIndex]; | |
var targetElement = targetList[targetIndex]; | |
// items are equal | |
if (sourceElement.CompareTo(targetElement) == 0) | |
{ | |
listItems.Add(CraeteElement(sourceList[sourceIndex], CompareState.Equal)); | |
sourceIndex++; | |
targetIndex++; | |
continue; | |
} | |
// source index was removed | |
if (sourceIndex < sourceList.Count - 1) | |
{ | |
var found = false; | |
var deletedItems = new List<ListItem>(); | |
deletedItems.Add(CraeteElement(sourceList[sourceIndex], CompareState.Deleted)); | |
for (var newSourceIndex = sourceIndex + 1; newSourceIndex < sourceList.Count; newSourceIndex++) | |
{ | |
if (sourceList[newSourceIndex].CompareTo(targetElement) == 0) | |
{ | |
sourceIndex = newSourceIndex; | |
listItems.AddRange(deletedItems); | |
found = true; | |
break; | |
} | |
else | |
{ | |
deletedItems.Add(CraeteElement(sourceList[newSourceIndex], CompareState.Deleted)); | |
} | |
} | |
if (found) | |
{ | |
continue; | |
} | |
} | |
// target index was added | |
if (targetIndex < targetList.Count - 1) | |
{ | |
var found = false; | |
var addedItems = new List<ListItem>(); | |
addedItems.Add(CraeteElement(targetList[targetIndex], CompareState.Added)); | |
for (var newTargetIndex = targetIndex + 1; newTargetIndex < targetList.Count; newTargetIndex++) | |
{ | |
if (targetList[newTargetIndex].CompareTo(sourceElement) == 0) | |
{ | |
targetIndex = newTargetIndex; | |
listItems.AddRange(addedItems); | |
found = true; | |
break; | |
} | |
else | |
{ | |
addedItems.Add(CraeteElement(targetList[newTargetIndex], CompareState.Added)); | |
} | |
} | |
if (found) | |
{ | |
continue; | |
} | |
} | |
// both elements changed | |
listItems.Add(CraeteElement(sourceElement, CompareState.Deleted)); | |
listItems.Add(CraeteElement(targetElement, CompareState.Added)); | |
sourceIndex++; | |
targetIndex++; | |
} | |
// all items in source list that left should be added as Deleted | |
listItems.AddRange(GetRemainingElements(sourceIndex, sourceList, CompareState.Deleted)); | |
// add all remaining items from target list with status Added | |
listItems.AddRange(GetRemainingElements(targetIndex, targetList, CompareState.Added)); | |
return listItems; | |
} | |
private IEnumerable<ListItem> GetRemainingElements(int currentIndex, List<IComparable> inputList, CompareState compareState) | |
{ | |
var listItems = new List<ListItem>(); | |
if (currentIndex < inputList.Count) | |
{ | |
for (var i = currentIndex; i < inputList.Count; i++) | |
{ | |
listItems.Add(CraeteElement(inputList[i], compareState)); | |
} | |
} | |
return listItems; | |
} | |
private ListItem CraeteElement( IComparable element, CompareState compareState) | |
{ | |
return new ListItem() | |
{ | |
Value = element, | |
State = compareState | |
}; | |
} | |
} | |
public class ListItem | |
{ | |
public IComparable Value { get; set; } | |
public CompareState State { get; set; } | |
} | |
public enum CompareState | |
{ | |
Undefined, | |
Equal, | |
Deleted, | |
Added | |
} | |
} |
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="compare" path="widget" /> | |
</paths> | |
</dojo> | |
<clientModule initializer="compare.ModuleInitializer"> | |
<moduleDependencies> | |
<add dependency="CMS" type="RunAfter" /> | |
</moduleDependencies> | |
</clientModule> | |
<clientResources> | |
<!-- Inject our custom Display Option selector --> | |
<add name="epi-cms.widgets.base" | |
path="~/modules/compare/EpiServerOverridenLibs/CustomCompareCommandProvider.js" | |
resourceType="Script" | |
/> | |
<add name="epi-cms.widgets.base" | |
path="~/modules/compare/EpiServerOverridenLibs/CustomWidgetSwitcher.js" | |
resourceType="Script" | |
/> | |
</clientResources> | |
</module> |
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 | |
"dojo", | |
"dojo/_base/declare", | |
//CMS | |
"epi/_Module", | |
"epi/dependency", | |
"epi/routes" | |
], function ( | |
// Dojo | |
dojo, | |
declare, | |
//CMS | |
_Module, | |
dependency, | |
routes | |
) { | |
return declare("compare.ModuleInitializer", [_Module], { | |
// summary: Module initializer for the default module. | |
initialize: function () { | |
this.inherited(arguments); | |
var registry = this.resolveDependency("epi.storeregistry"); | |
//Register the store | |
registry.create("compare.htmlDiff", this._getRestPath("htmlDiffStore")); | |
}, | |
_getRestPath: function (name) { | |
return routes.getRestPath({ moduleArea: "App", storeName: name }); | |
} | |
}); | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment